hecks 2.7.0 → 2.9.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -5,27 +5,8 @@ require "uri"
5
5
  module Hecks
6
6
  module Runtime
7
7
  module EraCheck
8
- # The read side of the era gate, for a deploy operator rather than a boot:
9
- # asks a running host which era it reports at `GET /version` and compares
10
- # it with an allow-list file of the eras that should be running.
11
- #
12
- # `EraCheck.check!` decides at boot, against the store, whether a host may
13
- # run on an era. This answers the question after a roll, from outside:
14
- # did the host that came up report an era the operator expects? The two
15
- # never share state. The host's own `GET /version` document
16
- # (`rust/host/src/server.rs`, `version_body`) is the only input, so a
17
- # rollback to an older image, or a fork of the journal back onto an older
18
- # era, shows up here as an era missing from the list.
19
- #
20
- # ## The allow-list file
21
- #
22
- # One era id per line. Blank lines and lines starting with `#` are
23
- # ignored, and so is a trailing ` # comment`. A file that lists no era
24
- # means "only check that the host reports one", so a first roll can be
25
- # checked before any era id is known. The generated `expected-era` file
26
- # (`Deploy::Scripts`) has this shape.
27
- #
28
- # `bin/check_era` is the command-line wrapper.
8
+ # Compares a running host's reported era (`GET /version`) against an
9
+ # allow-list file of eras that should be running, for use after a roll.
29
10
  module ExpectedEra
30
11
  # The host could not be reached, or did not answer with a `200`.
31
12
  class Unreachable < StandardError; end
@@ -36,7 +17,7 @@ module Hecks
36
17
  # What a comparison concluded.
37
18
  #
38
19
  # @!attribute [r] status
39
- # @return [Symbol] `:match`, `:unlisted` (no era on the list, one reported) or `:mismatch`
20
+ # @return [Symbol] `:match`, `:unlisted` (no era listed) or `:mismatch`
40
21
  # @!attribute [r] era
41
22
  # @return [String] the era the host reported
42
23
  # @!attribute [r] allowed
@@ -6,11 +6,7 @@ module Hecks
6
6
  module Ports
7
7
  module Persistence
8
8
  # Translates a journal entry written under an old shape into the one
9
- # the current bluebook declares — a renamed attribute, a renamed
10
- # aggregate, or a field crossing a value-object boundary. Applied
11
- # wherever entries are read, so replay derives the current head from
12
- # history that was never rewritten — the port owns the meaning,
13
- # adapters stay oblivious.
9
+ # the current bluebook declares; replay derives the head from unrewritten history.
14
10
  class Lineage
15
11
  # `ancestor_name` is the declared name a rename came from (matches
16
12
  # the held bluebook's own aggregate names); `ancestor_storage_name`
@@ -24,8 +20,8 @@ module Hecks
24
20
  # searched, in declaration order
25
21
  # @param domain [String, Symbol] name of the domain the aggregate belongs to
26
22
  # @param aggregate [Bluebook::Aggregate] the aggregate as currently declared
27
- # @return [Ports::Persistence::Lineage, nil] the rules of the first translation for `domain`
28
- # that names the aggregate; nil when none does
23
+ # @return [Ports::Persistence::Lineage, nil] the first translation for `domain` naming
24
+ # the aggregate; nil when none does
29
25
  def self.for(registry, domain, aggregate)
30
26
  translation = registry.translations.find do |candidate|
31
27
  candidate.domain == domain.to_s && candidate.for_aggregate(aggregate.name)
@@ -37,9 +33,8 @@ module Hecks
37
33
 
38
34
  # Builds the rules one declared translation edge carries for one aggregate.
39
35
  #
40
- # One specific edge's rules for one aggregate — what the mint
41
- # path uses, where `for` would happily answer with whichever
42
- # edge in the registry mentioned the aggregate first.
36
+ # Unlike `for`, this uses one specific edge, not whichever edge in
37
+ # the registry mentions the aggregate first — what the mint path needs.
43
38
  #
44
39
  # @param declared [Bluebook::TranslationAggregate, nil] the edge's entry for the
45
40
  # aggregate, as `Bluebook::Translation#for_aggregate` returns it; nil when the edge
@@ -60,24 +55,16 @@ module Hecks
60
55
  ancestor_name: ancestor_name, ancestor_storage_name: ancestor_storage_name)
61
56
  end
62
57
 
63
- # @param renames [Hash{Symbol => Symbol}] old top-level attribute name to new name
64
- # @param moves [Array<Bluebook::TranslationMove>] fields crossing a value-object
65
- # boundary, each a `from`/`to` pair of bare or dotted paths
66
- # @param converts [Array<Bluebook::TranslationConvert>] moves whose value is replaced
67
- # through an exhaustive `values` lookup table
68
- # @param drops [Array<Symbol>] bare or dotted paths whose data is deliberately discarded
69
- # @param retypes [Array<Bluebook::TranslationRetype>] value-object or entity type names
70
- # declared to mean the same shape
71
- # @param computes [Array<Bluebook::TranslationCompute>] SQL-only rules, never applied
72
- # in process
73
- # @param rekeys [Array<Bluebook::TranslationRekey>] SQL-only identity rewrites; only
74
- # the first is read
75
- # @param backfills [Array<Bluebook::TranslationBackfill>] defaults for new top-level
76
- # attributes an old entry lacks
77
- # @param ancestor_name [String, nil] the aggregate's declared name before the edge;
78
- # nil unless the edge renames the aggregate itself
79
- # @param ancestor_storage_name [String, nil] snake_case storage name of
80
- # `ancestor_name`; nil unless the edge renames the aggregate itself
58
+ # @param renames [Hash{Symbol => Symbol}] old attribute name to new name
59
+ # @param moves [Array<Bluebook::TranslationMove>] from/to paths across a value object
60
+ # @param converts [Array<Bluebook::TranslationConvert>] moves mapped through a values table
61
+ # @param drops [Array<Symbol>] bare or dotted paths whose data is discarded
62
+ # @param retypes [Array<Bluebook::TranslationRetype>] type names meaning the same shape
63
+ # @param computes [Array<Bluebook::TranslationCompute>] SQL-only rules, never applied here
64
+ # @param rekeys [Array<Bluebook::TranslationRekey>] SQL rewrites; only the first is read
65
+ # @param backfills [Array<Bluebook::TranslationBackfill>] defaults for a new attribute
66
+ # @param ancestor_name [String, nil] the aggregate's name before the edge, if renamed
67
+ # @param ancestor_storage_name [String, nil] snake_case storage name of ancestor_name
81
68
  def initialize(renames, moves = [], converts = [], drops = [], retypes: [], computes: [], rekeys: [],
82
69
  backfills: [], ancestor_name: nil, ancestor_storage_name: nil)
83
70
  @renames = renames
@@ -94,54 +81,39 @@ module Hecks
94
81
 
95
82
  # Reports whether any rule in this edge is a compute.
96
83
  #
97
- # Compute is the one rule kind with no in-process implementation at
98
- # all. An aggregate carrying one refuses to boot anywhere but
99
- # Postgres, per-rule and by name, before the general drift
100
- # machinery says anything vaguer.
84
+ # Compute has no in-process implementation; an aggregate carrying
85
+ # one refuses to boot anywhere but Postgres.
101
86
  #
102
87
  # @return [Boolean] true when the edge declares at least one compute rule
103
88
  def computes? = !@computes.empty?
104
89
 
105
90
  # Reports whether this edge rewrites the aggregate's identity.
106
91
  #
107
- # The single source of truth for "does this edge rekey this
108
- # aggregate" — every consumer (coverage_check.rb's identity gate,
109
- # minter.rb's approval gate, layer_two.rb's audit, head_compiler.rb's
110
- # SQL compilation) asks this, never re-derives it from `declared`
111
- # independently. One accessor to change if what a rekey rule means
112
- # ever needs to change, not four call sites in four files.
92
+ # The single source of truth for this; other call sites ask here
93
+ # rather than re-deriving it from `declared` themselves.
113
94
  #
114
95
  # @return [Boolean] true when the edge declares at least one rekey rule
115
96
  def rekey? = !@rekeys.empty?
116
97
 
117
98
  # Returns the SQL expression the edge's first rekey rule declares.
118
99
  #
119
- # The rekey's own SQL — first-and-only rule, same one-per-aggregate
120
- # assumption `compute` makes about its own list where it matters
121
- # (an edge with more than one is a DSL-level decision, not
122
- # something this reader arbitrates).
100
+ # Only the first rule is read, the same one-per-aggregate assumption
101
+ # `compute` makes; more than one is a DSL-level decision to arbitrate elsewhere.
123
102
  #
124
103
  # @return [String, nil] the first rekey rule's SQL; nil when the edge declares no rekey
125
104
  def rekey_sql = @rekeys.first&.sql
126
105
 
127
106
  # Rewrites one journal entry's state from the held shape into the current one.
128
107
  #
129
- # The reference semantics for the five portable rule kinds —
130
- # rename, move, convert, drop, and the aggregate-level `was:`.
131
- # `retype` moves nothing (stored state never carries a type name)
132
- # and `compute` is deliberately not applied here: its SQL is its
133
- # only implementation, so this transform neither imitates nor
134
- # checks it — the source field passes through untouched, and the
135
- # audit verifies compute output against the matview alone.
108
+ # The reference semantics for rename, move, convert, drop, and the aggregate-level `was:`.
109
+ # `compute` is not applied here — its SQL is the only implementation, audited separately.
136
110
  #
137
- # @param entry [Ports::Persistence::Entry] the entry as stored: `state` has Symbol
138
- # top-level keys and String keys inside a nested value-object Hash
139
- # @return [Ports::Persistence::Entry] a new entry with a deep-copied, translated state
140
- # and the same `operation`, `id` and `mirrors`; `entry` itself, untouched, when it
141
- # is not a save or carries no state
142
- # @raise [Runtime::WiringError] if a convert meets a value missing from its `values`
143
- # table, or a move or convert would nest under a destination already holding a
144
- # non-Hash value
111
+ # @param entry [Ports::Persistence::Entry] the entry as stored, with Symbol
112
+ # top-level keys and String keys nested in a value-object Hash
113
+ # @return [Ports::Persistence::Entry] a new entry with translated state and the same
114
+ # `operation`, `id` and `mirrors`; `entry` itself when it is not a save or has no state
115
+ # @raise [Runtime::WiringError] if a convert value is missing from its `values` table, or a
116
+ # move/convert would nest under a destination already holding a non-Hash value
145
117
  def translate(entry)
146
118
  return entry unless entry.save? && entry.state
147
119
 
@@ -153,45 +125,18 @@ module Hecks
153
125
  @moves.each { |move| apply_move(state, move) }
154
126
  @converts.each { |convert| apply_convert(state, convert) }
155
127
  @drops.each { |name| apply_drop(state, name) }
156
- # Last, and only where nothing already answered — a backfill
157
- # fills the gap a rename/move/convert left untouched, never
158
- # overwrites a value that already made it across. Dotted-path
159
- # aware, same as `apply_drop` above (`apply_backfill`'s own
160
- # header) — a bare name was the only shape this ever needed
161
- # until a value object gained new required members with zero
162
- # source data of their own (found live: a client site's Attendee
163
- # redesign, commit 4326dcd).
128
+ # Only fills a gap nothing else already filled; dotted-path aware, like apply_drop.
164
129
  @backfills.each { |backfill| apply_backfill(state, backfill) }
165
130
  Entry.new(operation: entry.operation, id: entry.id, state: state, mirrors: entry.mirrors)
166
131
  end
167
132
 
168
133
  # Reports whether some rule accounts for a held path that vanished or changed type.
169
134
  #
170
- # Whether this translation names `path` as an old key it accounts
171
- # for — the rename, move, or convert it came from, or an explicit
172
- # drop. `path` is a bare name ("cost") or a dotted value-object
173
- # member ("price.currency"); a rule covering the whole top-level
174
- # attribute (a rename, a top-level move/convert/drop) also covers
175
- # anything nested under it, since the whole value travels or goes
176
- # away together. This is what catches a field — or a value object's
177
- # own member — that vanished (or silently changed type) without
178
- # anything explaining it, even when some other field is covered.
135
+ # A rule covering a whole top-level attribute also covers anything nested
136
+ # under it; `backfills` only matches a whole name, since a backfill adds an
137
+ # attribute that is new outright, never a path that existed and moved.
179
138
  #
180
- # `backfills` matches on the whole name only, never a dotted
181
- # prefix — a backfill names a top-level attribute that is new
182
- # outright (nothing to be a prefix of on the held side), unlike
183
- # every rule above it, which explains a path that existed and
184
- # moved, converted, or vanished.
185
- #
186
- # One `||` chain over a closed, fixed set of rule kinds (renames,
187
- # moves, converts, drops, computes, backfills) — the same six this
188
- # file's other methods enumerate. Splitting each disjunct into its
189
- # own predicate would scatter one question ("does any rule explain
190
- # this path") across six same-shaped methods with nothing else to
191
- # do.
192
- #
193
- # @param path [String, Symbol] a bare attribute name or a dotted value-object member
194
- # path on the held side
139
+ # @param path [String, Symbol] a bare attribute name or a dotted value-object member path
195
140
  # @return [Boolean] true when a rename, move, convert, drop or compute names the path or
196
141
  # its top-level attribute as its source, or a backfill names the top-level attribute
197
142
  # rubocop:disable-next Metrics/CyclomaticComplexity
@@ -210,32 +155,11 @@ module Hecks
210
155
 
211
156
  # Reports whether some rule gives an existing record a value at a new attribute.
212
157
  #
213
- # The destination-side twin of `explains?` above, which only ever
214
- # asks about a rule's source. `unsafe_additions` asks a different
215
- # question — not "was this vanished path accounted for" but "does
216
- # an existing record end up with a value here" — and a move or
217
- # convert whose `to:` lands a old field inside a brand-new
218
- # top-level attribute (`weight` becoming `contents.weight` when
219
- # `Contents` did not exist before) fills that attribute for an
220
- # existing record exactly as a `backfill` would, even though
221
- # nothing named `contents` explains any vanished path. `compute`
222
- # counts on the same terms `explains?` already grants it
223
- # elsewhere in this file — Postgres-only and audited, not
224
- # actually applied by this method, the same gap the vanish side
225
- # already lives with.
158
+ # The destination-side twin of `explains?`, which checks a rule's source; this checks
159
+ # `@renames.value?` too, since a bare rename fills the destination unconditionally,
160
+ # the same way a backfill would.
226
161
  #
227
- # `@renames.value?` belongs here too: a bare
228
- # `rename :cost, to: :amount` is the plainest possible covering
229
- # rule there is (`translate` above applies it unconditionally, no
230
- # lookup table, no per-record ambiguity — simpler than a move or
231
- # convert), and without it `unsafe_additions` reports the new name
232
- # as an unexplained required addition on every rename-only edge,
233
- # the single most common translation shape there is. `explains?`
234
- # checks the source side (`@renames.key?`); this is the symmetric
235
- # destination-side check.
236
- #
237
- # @param path [String, Symbol] the name of a top-level attribute new in the current
238
- # shape
162
+ # @param path [String, Symbol] the name of a top-level attribute new in the current shape
239
163
  # @return [Boolean] true when a rename, move, convert or compute lands a value in that
240
164
  # attribute, or a backfill names it
241
165
  def fills?(path)
@@ -250,15 +174,11 @@ module Hecks
250
174
 
251
175
  # Reports whether a declared retype pairs two type names as the same shape.
252
176
  #
253
- # A retype covers a value object or entity whose own name changed
254
- # with its members intact. Nothing in the stored data carries the
255
- # type name, so this never moves a value; it only satisfies the
256
- # era diff's literal type-name comparison.
177
+ # Nothing in the stored data carries a type name, so this never moves a value;
178
+ # it only satisfies the era diff's literal type-name comparison.
257
179
  #
258
- # @param held_type [String, Bluebook::Reference] the type the held era declares,
259
- # compared by its `to_s`
260
- # @param current_type [String, Bluebook::Reference] the type declared now, compared by
261
- # its `to_s`
180
+ # @param held_type [String, Bluebook::Reference] the type the held era declares
181
+ # @param current_type [String, Bluebook::Reference] the type declared now
262
182
  # @return [Boolean] true when some retype rule runs from `held_type` to `current_type`
263
183
  def retype?(held_type, current_type)
264
184
  @retypes.any? { |retype| retype.from == held_type.to_s && retype.to == current_type.to_s }
@@ -274,30 +194,14 @@ module Hecks
274
194
  end
275
195
  end
276
196
 
277
- # M27 (docs/audits/2026-08-10-main-bug-audit.md,
278
- # docs/audits/2026-08-11-bug-triage.md) — simultaneous, not
279
- # sequential: `state[new] = state.delete(old)` per rename, run
280
- # one rule at a time against the same hash it was reading from,
281
- # loses data the instant one rule's destination is another
282
- # rule's source. Applied sequentially, a swap (`rename :a, to: :b`
283
- # alongside `rename :b, to: :a`) on `{a: 1, b: 2}` produces
284
- # `{a: 1}` — the first rule writes `b: 1` over the real `b: 2`
285
- # before the second rule ever gets a chance to read it, and the
286
- # value the whole edge is supposed to preserve (2, moved to
287
- # `:a`) is gone. The standard fix: snapshot every rule's old
288
- # key and value from `state` first, then remove every old key
289
- # and only then write every new key — a rename never reads a
290
- # key this same pass has already written to, so a swap or a
291
- # longer chain applies as one permutation, not a sequence of
292
- # edits each stepping on the last.
197
+ # Snapshots every rename's old key/value before writing any new key, so a
198
+ # swap (:a<->:b) applies as one permutation instead of losing data when one
199
+ # rule's destination is another's source.
293
200
  def apply_renames(state, renames)
294
201
  snapshot = renames.filter_map { |old_name, new_name| [old_name, new_name, state[old_name]] if state.key?(old_name) }
295
202
  snapshot.each { |old_name, _new_name, _value| state.delete(old_name) }
296
- # Not combinable (Style/CombinableLoops is disabled repo-wide, see
297
- # .rubocop.yml, for exactly this reason): a swap (:a<->:b) needs
298
- # every delete done before any write, or the first rename's write
299
- # becomes the second rename's delete target — see this method's
300
- # own comment above.
203
+ # Not combinable: every delete must finish before any write, or a swap's
204
+ # first write becomes its second delete target.
301
205
  snapshot.each { |_old_name, new_name, value| state[new_name] = value }
302
206
  end
303
207
 
@@ -317,18 +221,7 @@ module Hecks
317
221
  end
318
222
  end
319
223
 
320
- # Applies one backfill, dotted-path aware — `apply_drop`'s own
321
- # mirror on the addition side. A bare name sets a plain top-level
322
- # key, unchanged from before; a dotted name (`"attendee.
323
- # first_name"`) reaches into an existing value-object member the
324
- # same way `apply_drop`/`hecks_tr_insert` (rule_compiler.rb's own
325
- # SQL-compiled twin, kept in step with this) already do — creating
326
- # the container Hash if an old record somehow lacks it entirely,
327
- # never overwriting a value already there at either level.
328
- #
329
- # @param state [Hash] the entry's own state Hash, mutated in place
330
- # @param backfill [Bluebook::TranslationBackfill] the backfill rule to apply
331
- # @return [void]
224
+ # Dotted-path aware; must stay in step with `hecks_tr_insert` in rule_compiler.rb.
332
225
  def apply_backfill(state, backfill)
333
226
  name = backfill.name.to_s
334
227
  top, member = name.split(".", 2)
@@ -342,17 +235,8 @@ module Hecks
342
235
  end
343
236
  end
344
237
 
345
- # A dotted path's first segment is a top-level (symbol) key; a
346
- # second segment reaches into a value-object member by its string
347
- # key — the spelling a raw stored row carries. Translation runs on
348
- # raw rows, before the state codec decodes anything (PR A3): its
349
- # only caller (`Translation::Audit::LayerTwo`) feeds it
350
- # head-snapshot rows straight out of `JSON.parse`, and the
351
- # PostgresEra head applies the same rules in SQL before
352
- # `PostgresEra#decode` ever sees the jsonb. Decode is always the
353
- # last step, so an undeclared (retired) member this rule has to
354
- # read is still exactly as it was written, and never something an
355
- # adapter's `entries` — decoded, deep-symbol — would be fed here.
238
+ # Runs on raw rows, before the state codec decodes anything — decode is
239
+ # always the last step, so this never sees an adapter's deep-symbolized entry.
356
240
  def apply_move(state, move)
357
241
  old_top, old_member = move.from.split(".", 2)
358
242
  new_top, new_member = move.to.split(".", 2)
@@ -364,9 +248,8 @@ module Hecks
364
248
  insert(state, new_top.to_sym, new_member, value, rule: "move #{move.from} to: #{move.to}")
365
249
  end
366
250
 
367
- # A convert is a move whose value has nothing in common with its
368
- # replacement — the same path machinery, plus a lookup. A value
369
- # with no entry in the table refuses loudly rather than carry an
251
+ # A convert is a move whose value has nothing in common with its replacement.
252
+ # A value missing from the lookup table refuses loudly rather than carry an
370
253
  # unrecognized value silently into the new era.
371
254
  def apply_convert(state, convert)
372
255
  old_top, old_member = convert.from.split(".", 2)
@@ -397,18 +280,9 @@ module Hecks
397
280
  [value, true]
398
281
  end
399
282
 
400
- # Adversarial finding, not a hypothetical: a destination whose
401
- # top segment already holds a value — most commonly a reference,
402
- # stored as a bare scalar id — must not be nested under when a
403
- # dotted destination needs it (`state[top] ||= {}` alone only
404
- # guards nil/false, so a truthy non-Hash sails straight through
405
- # to `state[top][member] =`, i.e. `"team-1"["detail"] =`, which
406
- # is String#[]= and raises an unrelated-looking IndexError).
407
- # Whether it crashes or silently replaces the value, that is a
408
- # `drop` that never declared itself — the one thing this language
409
- # exists to make explicit (see apply_convert's own refusal above,
410
- # the same shape). Refuse by name instead, on both sides: the SQL
411
- # half (hecks_tr_insert) raises the identical wording.
283
+ # A destination already holding a non-Hash value (e.g. a bare reference id)
284
+ # must not be silently nested under — that would be an undeclared drop. The
285
+ # SQL half (`hecks_tr_insert`) refuses with identical wording.
412
286
  def insert(state, top, member, value, rule:)
413
287
  return state[top] = value unless member
414
288
 
@@ -0,0 +1,77 @@
1
+ module Hecks
2
+ module Adapters
3
+ class PostgresEra
4
+ # The shared `events` table: every domain's aggregates journal their emitted events
5
+ # into it, keyed by `aggregate` ("domain::AggregateName") and `aggregate_id`.
6
+ module Events
7
+ # Stores one emitted event in the `events` table, shared by every aggregate on this
8
+ # database and schema.
9
+ #
10
+ # @param event [Runtime::Event] the event to record; `correlation` is not stored
11
+ # @return [PG::Result] the INSERT's result
12
+ def record_event(event)
13
+ @db.exec_params(
14
+ "INSERT INTO events (name, aggregate, aggregate_id, payload, occurred_at) VALUES ($1, $2, $3, $4, $5)",
15
+ [event.name, event.aggregate, event.id.to_s, JSON.generate(event.payload), event.occurred_at]
16
+ )
17
+ end
18
+
19
+ # Reads every recorded event in the order recorded, across all aggregates.
20
+ #
21
+ # @return [Array<Runtime::Event>] events with symbol-keyed `payload` and `correlation` nil
22
+ def events
23
+ @db.exec("SELECT * FROM events ORDER BY id").map do |row|
24
+ Runtime::Event.new(
25
+ name: row["name"],
26
+ aggregate: row["aggregate"],
27
+ id: row["aggregate_id"],
28
+ payload: JSON.parse(row["payload"], symbolize_names: true),
29
+ occurred_at: row["occurred_at"]
30
+ )
31
+ end
32
+ end
33
+
34
+ # Reads back one record's recorded events, oldest first — pushed down as a `WHERE`
35
+ # clause (`hecks_events_aggregate_id_idx`) instead of filtering `#events`'s whole-table
36
+ # read, since a `corrects` command's history lookup only ever needs this one record.
37
+ #
38
+ # @param aggregate [String] the `"domain::AggregateName"` key events are stored under
39
+ # @param id [String, Object] the record's identity, matched as `id.to_s`
40
+ # @return [Array<Runtime::Event>] the record's stored events; `[]` when it has none
41
+ def events_for(aggregate:, id:)
42
+ @db.exec_params(
43
+ "SELECT * FROM events WHERE aggregate = $1 AND aggregate_id = $2 ORDER BY id",
44
+ [aggregate, id.to_s]
45
+ ).map do |row|
46
+ Runtime::Event.new(
47
+ name: row["name"],
48
+ aggregate: row["aggregate"],
49
+ id: row["aggregate_id"],
50
+ payload: JSON.parse(row["payload"], symbolize_names: true),
51
+ occurred_at: row["occurred_at"]
52
+ )
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def create_event_table!
59
+ @db.exec(<<~SQL)
60
+ CREATE TABLE IF NOT EXISTS events (
61
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
62
+ name text NOT NULL,
63
+ aggregate text NOT NULL,
64
+ aggregate_id text NOT NULL,
65
+ payload jsonb,
66
+ occurred_at text
67
+ )
68
+ SQL
69
+ # Backs #events_for's per-record lookup (a `corrects` command's history read).
70
+ @db.exec(
71
+ "CREATE INDEX IF NOT EXISTS hecks_events_aggregate_id_idx ON events (aggregate, aggregate_id)"
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end