hecks 3.13.0 → 4.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 (440) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
  7. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  8. data/examples/pizzas/config.ru +22 -0
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +1 -7
  12. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  13. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  14. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  15. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  16. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  17. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  18. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  19. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  20. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  21. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  22. data/lib/hecks/bluebook/translation.rb +18 -10
  23. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  24. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  25. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  26. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  27. data/lib/hecks/bluebook.rb +3 -0
  28. data/lib/hecks/chapters.rb +3 -3
  29. data/lib/hecks/cli/domain_stub.rb +1 -1
  30. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  31. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  32. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  33. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  34. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  35. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  36. data/lib/hecks/composer/adapters/jev.rb +66 -0
  37. data/lib/hecks/composer/agent.rb +113 -0
  38. data/lib/hecks/composer/assistant.rb +110 -0
  39. data/lib/hecks/composer/behavior_run.rb +53 -0
  40. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  41. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  42. data/lib/hecks/composer/board.rb +65 -0
  43. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  44. data/lib/hecks/composer/board_edit/source.rb +84 -0
  45. data/lib/hecks/composer/board_edit.rb +144 -0
  46. data/lib/hecks/composer/check.rb +89 -0
  47. data/lib/hecks/composer/domains.rb +81 -0
  48. data/lib/hecks/composer/jobs.rb +134 -0
  49. data/lib/hecks/composer/layout.rb +37 -0
  50. data/lib/hecks/composer/live.rb +150 -0
  51. data/lib/hecks/composer/outline.rb +48 -0
  52. data/lib/hecks/composer/page.html +989 -0
  53. data/lib/hecks/composer/panels.rb +142 -0
  54. data/lib/hecks/composer/parse.rb +83 -0
  55. data/lib/hecks/composer/plain.rb +28 -0
  56. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  57. data/lib/hecks/composer/session.rb +141 -0
  58. data/lib/hecks/composer/toolbox.rb +166 -0
  59. data/lib/hecks/composer/web.rb +156 -0
  60. data/lib/hecks/composer/workbench.rb +51 -0
  61. data/lib/hecks/composer/workspace.rb +81 -0
  62. data/lib/hecks/composer.rb +17 -0
  63. data/lib/hecks/corpus/adapters.rb +22 -0
  64. data/lib/hecks/corpus/rust.rb +1 -1
  65. data/lib/hecks/corpus/worlds.rb +19 -0
  66. data/lib/hecks/corpus.rb +5 -0
  67. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
  68. data/lib/hecks/forms/app/commands.rb +2 -3
  69. data/lib/hecks/forms/app/queries.rb +2 -3
  70. data/lib/hecks/forms/app/records.rb +17 -5
  71. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  72. data/lib/hecks/forms/field_controls.rb +2 -2
  73. data/lib/hecks/forms/field_renderer.rb +1 -1
  74. data/lib/hecks/forms/field_shape.rb +36 -17
  75. data/lib/hecks/forms/index_renderer.rb +2 -1
  76. data/lib/hecks/forms/page.rb +1 -1
  77. data/lib/hecks/forms/params.rb +0 -13
  78. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  79. data/lib/hecks/forms/record_renderer.rb +11 -7
  80. data/lib/hecks/forms/rule_words.rb +100 -0
  81. data/lib/hecks/forms/value_object_shape.rb +2 -1
  82. data/lib/hecks/framework/bluebook/change.bluebook +2 -2
  83. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  84. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  85. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  86. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  87. data/lib/hecks/gate/stages.yml +25 -0
  88. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  89. data/lib/hecks/grammar/evolve.rb +0 -3
  90. data/lib/hecks/grammar/translation.bluebook +1 -1
  91. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  92. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  93. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  94. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  95. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  96. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  97. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  98. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  99. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  100. data/lib/hecks/hecks/codebase.behaviors +6 -0
  101. data/lib/hecks/hecks/codebase.bluebook +58 -37
  102. data/lib/hecks/hecks/composer.behaviors +68 -0
  103. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  104. data/lib/hecks/hecks/custodian.behaviors +0 -6
  105. data/lib/hecks/hecks/custodian.bluebook +15 -92
  106. data/lib/hecks/hecks/hecks.bluebook +2 -2
  107. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  108. data/lib/hecks/hecks/hecks.world +1 -1
  109. data/lib/hecks/hecks/rehearsal.bluebook +10 -16
  110. data/lib/hecks/hecks/tooling.bluebook +22 -22
  111. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  112. data/lib/hecks/kernel_capabilities.rb +1 -1
  113. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  114. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  115. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  116. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  117. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  118. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  119. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  120. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  121. data/lib/hecks/ports/access_control.rb +2 -2
  122. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  123. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  124. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  125. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  126. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  127. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  128. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  129. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  130. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  131. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  132. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  133. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  134. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  135. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  136. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  137. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  138. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  139. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  140. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  141. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  142. data/lib/hecks/ports.rb +0 -1
  143. data/lib/hecks/projection_files.rb +3 -1
  144. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  145. data/lib/hecks/projections/glossary/entries.rb +10 -4
  146. data/lib/hecks/projections/glossary/groups.rb +42 -0
  147. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  148. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  149. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  150. data/lib/hecks/projections/glossary/html.rb +1 -2
  151. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  152. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  153. data/lib/hecks/projections/glossary/page.css +143 -153
  154. data/lib/hecks/projections/glossary/page.js +63 -3
  155. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  156. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  157. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  158. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  159. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  160. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  161. data/lib/hecks/projections/glossary.rb +2 -1
  162. data/lib/hecks/projections/plain_english.rb +97 -0
  163. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  164. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  165. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  166. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  167. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  168. data/lib/hecks/projections/statements/actions.rb +99 -0
  169. data/lib/hecks/projections/statements/relationships.rb +86 -0
  170. data/lib/hecks/projections/statements.rb +15 -24
  171. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  172. data/lib/hecks/projector/exporter/translations.rb +18 -35
  173. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  174. data/lib/hecks/projector/exporter.rb +5 -1
  175. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  176. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  177. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  178. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  179. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  180. data/lib/hecks/runtime/outbox.rb +2 -9
  181. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  182. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  183. data/lib/hecks/runtime/value.rb +13 -0
  184. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  185. data/lib/hecks/rust_build/wasm.rb +0 -12
  186. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  187. data/lib/hecks/rust_format.rb +77 -0
  188. data/lib/hecks/site/bluebook/site.bluebook +5 -5
  189. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  190. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  191. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  192. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  193. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  194. data/lib/hecks/tools/translation_audit.rb +8 -10
  195. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  196. data/lib/hecks/version.rb +1 -1
  197. data/lib/hecks/vocabulary.rb +2 -1
  198. data/lib/hecks.rb +3 -0
  199. data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  200. data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  201. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
  202. data/rust/Cargo.toml +40 -0
  203. data/rust/build/Cargo.toml +36 -0
  204. data/rust/build/clippy.toml +4 -0
  205. data/rust/build/src/adapters_pass.rs +196 -0
  206. data/rust/build/src/binding_pass.rs +54 -13
  207. data/rust/build/src/build_artifact.rs +23 -5
  208. data/rust/build/src/cargo_sync.rs +69 -26
  209. data/rust/build/src/lineage_pass.rs +101 -87
  210. data/rust/build/src/main.rs +11 -2
  211. data/rust/build/src/pipeline.rs +80 -19
  212. data/rust/build/src/reserved_names.rs +6 -50
  213. data/rust/build/src/resolve.rs +82 -35
  214. data/rust/build/src/rustfmt.rs +100 -0
  215. data/rust/build/src/seams_pass.rs +85 -16
  216. data/rust/build/src/subprocess.rs +30 -6
  217. data/rust/build/src/tmp.rs +5 -2
  218. data/rust/build/src/translation_pass.rs +72 -22
  219. data/rust/build/src/world_pass.rs +109 -0
  220. data/rust/clippy.toml +4 -0
  221. data/rust/codegen/Cargo.toml +36 -0
  222. data/rust/codegen/clippy.toml +4 -0
  223. data/rust/codegen/src/asks.rs +40 -14
  224. data/rust/codegen/src/attr.rs +2 -2
  225. data/rust/codegen/src/bridging.rs +676 -117
  226. data/rust/codegen/src/commands.rs +979 -373
  227. data/rust/codegen/src/constraints.rs +88 -25
  228. data/rust/codegen/src/dependency_planning.rs +128 -34
  229. data/rust/codegen/src/domain_generator.rs +300 -170
  230. data/rust/codegen/src/exemplar.rs +87 -32
  231. data/rust/codegen/src/expr_emitter.rs +54 -6
  232. data/rust/codegen/src/fielded.rs +193 -41
  233. data/rust/codegen/src/hecks_naming.rs +124 -16
  234. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  235. data/rust/codegen/src/json.rs +80 -44
  236. data/rust/codegen/src/json_codec.rs +585 -149
  237. data/rust/codegen/src/literal.rs +104 -138
  238. data/rust/codegen/src/main.rs +145 -52
  239. data/rust/codegen/src/manifest.rs +76 -14
  240. data/rust/codegen/src/mutations.rs +1212 -239
  241. data/rust/codegen/src/naming.rs +111 -60
  242. data/rust/codegen/src/optional_pass.rs +90 -28
  243. data/rust/codegen/src/ports.rs +5 -4
  244. data/rust/codegen/src/prelude.rs +139 -49
  245. data/rust/codegen/src/queries.rs +601 -226
  246. data/rust/codegen/src/reactions.rs +268 -74
  247. data/rust/codegen/src/read_models.rs +606 -205
  248. data/rust/codegen/src/reference_specs.rs +16 -3
  249. data/rust/codegen/src/registry.rs +128 -79
  250. data/rust/codegen/src/reserved_names.rs +6 -50
  251. data/rust/codegen/src/shared.rs +71 -22
  252. data/rust/codegen/src/sidecars.rs +15 -5
  253. data/rust/codegen/src/skip_reason.rs +8 -2
  254. data/rust/codegen/src/types.rs +306 -108
  255. data/rust/host/Cargo.lock +1 -0
  256. data/rust/host/Cargo.toml +40 -4
  257. data/rust/host/HECKS_RELEASE +1 -1
  258. data/rust/host/clippy.toml +4 -0
  259. data/rust/host/src/api.rs +698 -218
  260. data/rust/host/src/auth.rs +491 -214
  261. data/rust/host/src/b64.rs +91 -0
  262. data/rust/host/src/bin/expr_harness.rs +6 -2
  263. data/rust/host/src/bin/lineage_harness.rs +47 -11
  264. data/rust/host/src/bin/mint_harness.rs +70 -37
  265. data/rust/host/src/bin/validate_harness.rs +16 -8
  266. data/rust/host/src/boot.rs +144 -68
  267. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  268. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  269. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  270. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  271. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  272. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  273. data/rust/host/src/checkout.rs +78 -32
  274. data/rust/host/src/clock.rs +8 -0
  275. data/rust/host/src/commerce_ir.rs +333 -95
  276. data/rust/host/src/crypto.rs +33 -0
  277. data/rust/host/src/dispatch.rs +990 -331
  278. data/rust/host/src/expr_json.rs +1021 -273
  279. data/rust/host/src/extension.rs +24 -4
  280. data/rust/host/src/field_hints.rs +11 -7
  281. data/rust/host/src/fuzz_support.rs +85 -13
  282. data/rust/host/src/host.rs +100 -0
  283. data/rust/host/src/http.rs +22 -0
  284. data/rust/host/src/ir.rs +326 -16
  285. data/rust/host/src/journal.rs +277 -176
  286. data/rust/host/src/lambda_client.rs +219 -53
  287. data/rust/host/src/lib.rs +16 -4
  288. data/rust/host/src/log.rs +62 -11
  289. data/rust/host/src/mint.rs +1153 -326
  290. data/rust/host/src/needs.rs +185 -44
  291. data/rust/host/src/payments/keystore.rs +197 -45
  292. data/rust/host/src/payments/roles.rs +191 -0
  293. data/rust/host/src/payments/tests.rs +1445 -275
  294. data/rust/host/src/payments.rs +409 -121
  295. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  296. data/rust/host/src/platform_signin/config.rs +11 -11
  297. data/rust/host/src/platform_signin/cookie.rs +2 -2
  298. data/rust/host/src/platform_signin/extension.rs +2 -1
  299. data/rust/host/src/platform_signin/handler.rs +9 -8
  300. data/rust/host/src/platform_signin/jti.rs +4 -2
  301. data/rust/host/src/platform_signin/jwks.rs +2 -1
  302. data/rust/host/src/platform_signin/jws.rs +2 -37
  303. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  304. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  305. data/rust/host/src/platform_signin/pages.rs +20 -20
  306. data/rust/host/src/platform_signin/session.rs +8 -1
  307. data/rust/host/src/platform_signin/settings.rs +6 -6
  308. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  309. data/rust/host/src/platform_signin/verify.rs +41 -41
  310. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  311. data/rust/host/src/presentation.rs +207 -63
  312. data/rust/host/src/presentation_write.rs +502 -187
  313. data/rust/host/src/query_step.rs +69 -18
  314. data/rust/host/src/rate_limit.rs +541 -142
  315. data/rust/host/src/reference_transform.rs +77 -25
  316. data/rust/host/src/reference_validate.rs +443 -104
  317. data/rust/host/src/resend.rs +190 -55
  318. data/rust/host/src/secrets.rs +116 -21
  319. data/rust/host/src/server.rs +483 -104
  320. data/rust/host/src/storage_shape.rs +88 -21
  321. data/rust/host/src/test_pg.rs +17 -7
  322. data/rust/host/src/ui_schema.rs +495 -207
  323. data/rust/host/src/wasm_runner.rs +21 -6
  324. data/rust/host/src/web/commerce.rs +101 -23
  325. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  326. data/rust/host/src/web/newsletter/template.rs +26 -7
  327. data/rust/host/src/web/newsletter/tests.rs +112 -23
  328. data/rust/host/src/web/newsletter.rs +209 -59
  329. data/rust/host/src/web/newsletter_send.rs +251 -79
  330. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  331. data/rust/host/src/web/registration_receipt.rs +100 -24
  332. data/rust/host/src/web/registrations.rs +1384 -332
  333. data/rust/host/src/web/resend_webhook.rs +138 -80
  334. data/rust/host/src/web.rs +2416 -508
  335. data/rust/lsp/Cargo.toml +36 -0
  336. data/rust/lsp/clippy.toml +4 -0
  337. data/rust/lsp/src/json.rs +29 -22
  338. data/rust/lsp/src/main.rs +81 -31
  339. data/rust/lsp/src/outline.rs +23 -7
  340. data/rust/lsp/src/rpc.rs +17 -4
  341. data/rust/parser/Cargo.toml +36 -0
  342. data/rust/parser/clippy.toml +4 -0
  343. data/rust/parser/src/build/closed_sets.rs +6 -1
  344. data/rust/parser/src/build/identity.rs +4 -3
  345. data/rust/parser/src/build/mod.rs +1 -0
  346. data/rust/parser/src/build/named_pattern.rs +26 -0
  347. data/rust/parser/src/build/naming.rs +2 -80
  348. data/rust/parser/src/build/pattern_subset.rs +6 -8
  349. data/rust/parser/src/build/query_derive.rs +3 -1
  350. data/rust/parser/src/build/query_inference.rs +14 -5
  351. data/rust/parser/src/build/read_model.rs +4 -1
  352. data/rust/parser/src/build/references.rs +4 -3
  353. data/rust/parser/src/canonical.rs +34 -15
  354. data/rust/parser/src/emit.rs +187 -105
  355. data/rust/parser/src/expr/ast_json.rs +134 -42
  356. data/rust/parser/src/expr/evaluator.rs +27 -9
  357. data/rust/parser/src/expr/mod.rs +54 -13
  358. data/rust/parser/src/expr/resolver.rs +299 -92
  359. data/rust/parser/src/ir.rs +35 -3
  360. data/rust/parser/src/keywords.rs +7168 -510
  361. data/rust/parser/src/lex.rs +11 -25
  362. data/rust/parser/src/lib.rs +1 -0
  363. data/rust/parser/src/main.rs +65 -6
  364. data/rust/parser/src/parse/adapter.rs +157 -0
  365. data/rust/parser/src/parse/aggregate.rs +19 -10
  366. data/rust/parser/src/parse/chapter.rs +16 -17
  367. data/rust/parser/src/parse/command.rs +35 -16
  368. data/rust/parser/src/parse/domain_port.rs +4 -7
  369. data/rust/parser/src/parse/entity.rs +21 -11
  370. data/rust/parser/src/parse/hecksagon.rs +10 -4
  371. data/rust/parser/src/parse/lifecycle.rs +9 -5
  372. data/rust/parser/src/parse/mod.rs +43 -91
  373. data/rust/parser/src/parse/needs.rs +21 -12
  374. data/rust/parser/src/parse/policy.rs +2 -2
  375. data/rust/parser/src/parse/process_manager.rs +9 -11
  376. data/rust/parser/src/parse/query.rs +8 -4
  377. data/rust/parser/src/parse/read_model.rs +57 -16
  378. data/rust/parser/src/parse/value_object.rs +7 -2
  379. data/rust/parser/src/parse/world.rs +322 -0
  380. data/rust/parser/src/ruby_value.rs +7 -6
  381. data/rust/src/exemplar/json.rs +4 -4
  382. data/rust/src/exemplar/mutations.rs +18 -0
  383. data/rust/src/exemplar/read_models.rs +1 -10
  384. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  385. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  386. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  387. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  388. data/rust/src/kernel/cli.rs +229 -112
  389. data/rust/src/kernel/dispatch.rs +323 -115
  390. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  391. data/rust/src/kernel/expr.rs +203 -86
  392. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  393. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  394. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  395. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  396. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  397. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  398. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  399. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  400. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  401. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  402. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  403. data/rust/src/kernel/expression_operators/text.rs +120 -27
  404. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  405. data/rust/src/kernel/json.rs +230 -148
  406. data/rust/src/kernel/mod.rs +46 -33
  407. data/rust/src/kernel/named_query.rs +63 -15
  408. data/rust/src/kernel/needs.rs +112 -25
  409. data/rust/src/kernel/orchestrate.rs +980 -581
  410. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  411. data/rust/src/kernel/pattern.rs +107 -34
  412. data/rust/src/kernel/query_comparators.rs +118 -41
  413. data/rust/src/kernel/query_ordering.rs +13 -5
  414. data/rust/src/kernel/read_model.rs +479 -181
  415. data/rust/src/kernel/reference_lookup.rs +122 -26
  416. data/rust/src/kernel/repository.rs +293 -66
  417. data/rust/src/kernel/routing.rs +58 -42
  418. data/rust/src/kernel/ruby_float.rs +139 -0
  419. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  420. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  421. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  422. data/rust/src/kernel/vocab/mod.rs +6 -6
  423. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  424. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  425. data/rust/src/lib.rs +50 -0
  426. data/rust/src/main.rs +4 -1
  427. data/rust/web/Cargo.toml +36 -0
  428. data/rust/web/clippy.toml +4 -0
  429. metadata +85 -15
  430. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  431. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  432. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  433. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  434. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  435. data/lib/hecks/ports/authentication.port +0 -6
  436. data/lib/hecks/ports/authentication.rb +0 -67
  437. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  438. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  439. data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
  440. data/rust/host/src/approval.rs +0 -652
@@ -14,7 +14,7 @@ module Hecks
14
14
  module EraCheck
15
15
  module_function
16
16
 
17
- # Runs both halves of the era gate over every bluebook in a registry.
17
+ # Runs the era gate over every bluebook in a registry.
18
18
  #
19
19
  # Looks up each bluebook's own source separately: `attaches` can share a boot between
20
20
  # the domain's own bluebook and a differently-sourced one, so one file read once and reused
@@ -23,27 +23,12 @@ module Hecks
23
23
  # @param registry [Runtime::Registry] the registry being booted
24
24
  # @param directory [String] the domain's bluebook directory, searched for each source
25
25
  # @return [void]
26
- # @raise [Runtime::WiringError] if a compute rule is bound to a non-lineage-capable adapter,
27
- # a persistence binding cannot be resolved, a lineage-bound bluebook has no findable
28
- # source, or `era_check!` refuses the boot
26
+ # @raise [Runtime::WiringError] if a persistence binding cannot be resolved, a lineage-bound
27
+ # bluebook has no findable source, or `era_check!` refuses the boot
29
28
  def check!(registry, directory)
30
- check_compute_rules_for_registry!(registry)
31
29
  check_lineage!(registry, directory)
32
30
  end
33
31
 
34
- # Refuses the boot when any bluebook's compute rule is bound away from Postgres.
35
- #
36
- # Runs unconditionally for every registry, unlike check_lineage!'s
37
- # capability-gated pass below (ADR 0031).
38
- #
39
- # @param registry [Runtime::Registry] the registry whose bluebooks are all checked
40
- # @return [void]
41
- # @raise [Runtime::WiringError] if an aggregate carrying a compute rule is bound to an
42
- # adapter that is not lineage-capable, or its persistence binding cannot be resolved
43
- def check_compute_rules_for_registry!(registry)
44
- registry.bluebooks.each_value { |bluebook| check_compute_rules!(registry, bluebook) }
45
- end
46
-
47
32
  # Hands every bluebook, with its own source text, to its adapter's era check.
48
33
  #
49
34
  # Capability-gated (ADR 0031): check_bluebook! below still returns
@@ -222,29 +207,6 @@ module Hecks
222
207
  "it could be found (checked #{directory.inspect} and the framework registry)"
223
208
  end
224
209
 
225
- # Refuses the boot when one bluebook's compute rule is bound away from Postgres.
226
- #
227
- # A compute rule's SQL is its only implementation, so it cannot
228
- # boot on any adapter but Postgres, independent of shape checks.
229
- #
230
- # @param registry [Runtime::Registry] the registry holding the declared translations
231
- # and the aggregate's bindings
232
- # @param bluebook [Bluebook::Chapter] the bluebook whose aggregates are checked
233
- # @return [void]
234
- # @raise [Runtime::WiringError] if an aggregate carrying a compute rule is bound to an
235
- # adapter that is not lineage-capable, or its persistence binding cannot be resolved
236
- def check_compute_rules!(registry, bluebook)
237
- bluebook.aggregates.each do |aggregate|
238
- lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
239
- next unless lineage&.computes?
240
-
241
- adapter = adapter_for(registry, bluebook.name, aggregate)
242
- next if lineage_capable?(registry, adapter)
243
-
244
- raise WiringError, "compute rules require the Postgres adapter; #{aggregate.name} is bound to #{adapter}"
245
- end
246
- end
247
-
248
210
  # Names the adapter an aggregate's authoritative persistence bind points at.
249
211
  #
250
212
  # @param registry [Runtime::Registry] the registry holding the domain's hecksagon
@@ -12,7 +12,7 @@ module Hecks
12
12
  # attribute that is new outright, never a path that existed and moved.
13
13
  #
14
14
  # @param path [String, Symbol] a bare attribute name or a dotted value-object member path
15
- # @return [Boolean] true when a rename, move, convert, drop or compute names the path or
15
+ # @return [Boolean] true when a rename, move, convert, or drop names the path or
16
16
  # its top-level attribute as its source, or a backfill names the top-level attribute
17
17
  def explains?(path)
18
18
  path = path.to_s
@@ -30,8 +30,8 @@ module Hecks
30
30
  # the same way a backfill would.
31
31
  #
32
32
  # @param path [String, Symbol] the name of a top-level attribute new in the current shape
33
- # @return [Boolean] true when a rename, move, convert or compute lands a value in that
34
- # attribute, or a backfill names it
33
+ # @return [Boolean] true when a rename, move, convert or derive lands a value in
34
+ # that attribute, or a backfill names it
35
35
  def fills?(path)
36
36
  path = path.to_s
37
37
 
@@ -42,13 +42,13 @@ module Hecks
42
42
 
43
43
  private
44
44
 
45
- # Every path a move, convert, compute or drop reads from.
45
+ # Every path a move, convert or drop reads from.
46
46
  def sources
47
- [*@moves, *@converts, *@rules.computes].map(&:from) + @drops.map(&:to_s)
47
+ [*@moves, *@converts].map(&:from) + @drops.map(&:to_s)
48
48
  end
49
49
 
50
- # Every path a move, convert or compute writes to.
51
- def destinations = [*@moves, *@converts, *@rules.computes].map(&:to)
50
+ # Every path a move, convert or derive writes to.
51
+ def destinations = [*@moves, *@converts, *@rules.derives].map(&:to)
52
52
 
53
53
  def backfilled?(name) = @rules.backfills.any? { |backfill| backfill.name.to_s == name }
54
54
  end
@@ -2,12 +2,13 @@ module Hecks
2
2
  module Ports
3
3
  module Persistence
4
4
  class Lineage
5
- # The reference semantics of rename, move, convert, drop and backfill, applied in that
6
- # order to a deep copy of one entry's state.
5
+ # The reference semantics of derive, rename, move, convert, drop and backfill, applied in
6
+ # that order to a deep copy of one entry's state.
7
7
  module Rewrite
8
8
  private
9
9
 
10
10
  def rewrite(state)
11
+ apply_derives(state)
11
12
  apply_renames(state, @renames)
12
13
  @moves.each { |move| apply_move(state, move) }
13
14
  @converts.each { |convert| apply_convert(state, convert) }
@@ -17,6 +18,28 @@ module Hecks
17
18
  state
18
19
  end
19
20
 
21
+ # Every derive reads the state as stored, so the values are all computed before the
22
+ # first is written; a derive with no value writes nothing.
23
+ def apply_derives(state)
24
+ computed = @rules.derives.map { |derive| [derive, Bluebook::TranslationExpression.evaluate(derive.tree, state)] }
25
+ computed.each do |derive, value|
26
+ next if value.nil?
27
+
28
+ top, member = derive.to.split(".", 2)
29
+ insert(state, top.to_sym, member, value, rule: "derive #{derive.to}")
30
+ end
31
+ end
32
+
33
+ # The id a `rekey_to` gives a record: the value of its derive, read from the stored state;
34
+ # the record keeps its id where the derive has no value or the edge has no `rekey_to`.
35
+ def rekeyed_id(entry)
36
+ rekey = @rules.rekeys.first
37
+ return entry.id unless rekey
38
+
39
+ derive = @rules.derives.find { |candidate| candidate.to == rekey.to }
40
+ Bluebook::TranslationExpression.evaluate(derive.tree, entry.state) || entry.id
41
+ end
42
+
20
43
  def deep_dup(node)
21
44
  case node
22
45
  when Hash then node.transform_values { |value| deep_dup(value) }
@@ -6,18 +6,18 @@ module Hecks
6
6
  # an unknown keyword raises ArgumentError.
7
7
  #
8
8
  # - `retypes` — type names meaning the same shape
9
- # - `computes` — SQL-only rules, never applied in process
10
- # - `rekeys` — SQL rewrites of identity; only the first is read
9
+ # - `derives` — new fields computed from the record's own text
10
+ # - `rekeys` - the field whose derived value becomes the id
11
11
  # - `backfills` — defaults for a new attribute
12
12
  # - `ancestor_name` — the aggregate's name before the edge, if renamed
13
13
  # - `ancestor_storage_name` — snake_case storage name of `ancestor_name`
14
- Rules = Struct.new(:retypes, :computes, :rekeys, :backfills, :ancestor_name, :ancestor_storage_name,
14
+ Rules = Struct.new(:retypes, :derives, :rekeys, :backfills, :ancestor_name, :ancestor_storage_name,
15
15
  keyword_init: true) do
16
16
  # Builds the rules with every list defaulting to empty.
17
17
  #
18
18
  # @param given [Hash] any of the members; an unknown key raises ArgumentError
19
19
  # @return [Rules]
20
- def self.build(**given) = new(retypes: [], computes: [], rekeys: [], backfills: [], **given)
20
+ def self.build(**given) = new(retypes: [], derives: [], rekeys: [], backfills: [], **given)
21
21
  end
22
22
  end
23
23
  end
@@ -59,17 +59,23 @@ module Hecks
59
59
  ancestor_storage_name = renamed_aggregate ? Naming.snake(declared.was) : nil
60
60
 
61
61
  new(declared.renames, declared.moves, declared.converts, declared.drops,
62
- retypes: declared.retypes, computes: declared.computes, rekeys: declared.rekeys,
63
- backfills: declared.backfills,
64
- ancestor_name: ancestor_name, ancestor_storage_name: ancestor_storage_name)
62
+ **optional_rules(declared), ancestor_name: ancestor_name, ancestor_storage_name: ancestor_storage_name)
65
63
  end
66
64
 
65
+ # The rule lists an edge carries beyond renames, moves, converts and drops.
66
+ def self.optional_rules(declared)
67
+ { retypes: declared.retypes, derives: declared.derives,
68
+ rekeys: declared.rekeys, backfills: declared.backfills }
69
+ end
70
+ private_class_method :optional_rules
71
+
67
72
  # @param renames [Hash{Symbol => Symbol}] old attribute name to new name
68
73
  # @param moves [Array<Bluebook::TranslationMove>] from/to paths across a value object
69
74
  # @param converts [Array<Bluebook::TranslationConvert>] moves mapped through a values table
70
75
  # @param drops [Array<Symbol>] bare or dotted paths whose data is discarded
71
76
  # @param rules [Hash] the optional rule lists, as `Rules` names them: `retypes:`,
72
- # `computes:`, `rekeys:`, `backfills:`, `ancestor_name:` and `ancestor_storage_name:`
77
+ # `derives:`, `rekeys:`, `backfills:`, `ancestor_name:` and
78
+ # `ancestor_storage_name:`
73
79
  # @raise [ArgumentError] on a keyword `Rules` does not name
74
80
  def initialize(renames, moves = [], converts = [], drops = [], **rules)
75
81
  @renames = renames
@@ -79,14 +85,6 @@ module Hecks
79
85
  @rules = Rules.build(**rules)
80
86
  end
81
87
 
82
- # Reports whether any rule in this edge is a compute.
83
- #
84
- # Compute has no in-process implementation; an aggregate carrying
85
- # one refuses to boot anywhere but Postgres.
86
- #
87
- # @return [Boolean] true when the edge declares at least one compute rule
88
- def computes? = !@rules.computes.empty?
89
-
90
88
  # Reports whether this edge rewrites the aggregate's identity.
91
89
  #
92
90
  # The single source of truth for this; other call sites ask here
@@ -95,23 +93,16 @@ module Hecks
95
93
  # @return [Boolean] true when the edge declares at least one rekey rule
96
94
  def rekey? = !@rules.rekeys.empty?
97
95
 
98
- # Returns the SQL expression the edge's first rekey rule declares.
99
- #
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.
102
- #
103
- # @return [String, nil] the first rekey rule's SQL; nil when the edge declares no rekey
104
- def rekey_sql = @rules.rekeys.first&.sql
105
-
106
- # Rewrites one journal entry's state from the held shape into the current one.
96
+ # Rewrites one journal entry from the held shape into the current one.
107
97
  #
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.
98
+ # The reference semantics for derive, rename, move, convert, drop, `was:` and the id a
99
+ # `rekey_to` takes.
110
100
  #
111
101
  # @param entry [Ports::Persistence::Entry] the entry as stored, with Symbol
112
102
  # top-level keys and String keys nested in a value-object Hash
113
103
  # @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
104
+ # `operation` and `mirrors`, and the same `id` unless a `rekey_to` gives it a value;
105
+ # `entry` itself when it is not a save or has no state
115
106
  # @raise [Runtime::WiringError] if a convert value is missing from its `values` table, or a
116
107
  # move/convert would nest under a destination already holding a non-Hash value
117
108
  def translate(entry)
@@ -120,8 +111,9 @@ module Hecks
120
111
  # Deep, not shallow: a move or convert reaches into a nested
121
112
  # value-object hash, and a shallow dup would quietly mutate
122
113
  # the caller's copy of the original entry.
114
+ id = rekeyed_id(entry)
123
115
  state = rewrite(deep_dup(entry.state))
124
- Entry.new(operation: entry.operation, id: entry.id, state: state, mirrors: entry.mirrors)
116
+ Entry.new(operation: entry.operation, id: id, state: state, mirrors: entry.mirrors)
125
117
  end
126
118
 
127
119
  # Reports whether a declared retype pairs two type names as the same shape.
@@ -29,18 +29,6 @@ module Hecks
29
29
  )
30
30
  SQL
31
31
 
32
- # The Layer-3 approvals of translation edges.
33
- APPROVALS_SQL = <<~SQL.freeze
34
- CREATE TABLE IF NOT EXISTS hecks_approvals (
35
- domain text NOT NULL,
36
- from_label text NOT NULL,
37
- to_label text NOT NULL,
38
- edge_digest text NOT NULL,
39
- reviewed_ordinal bigint NOT NULL,
40
- approved_at timestamptz NOT NULL DEFAULT now()
41
- )
42
- SQL
43
-
44
32
  # An owned sequence default, not generated always as identity, which
45
33
  # partitioned tables only support from Postgres 17.
46
34
  JOURNAL_SQL = <<~SQL.freeze
@@ -76,7 +76,7 @@ module Hecks
76
76
  end
77
77
 
78
78
  def rename_rows!
79
- %w[hecks_eras hecks_era_texts hecks_approvals].each do |table|
79
+ %w[hecks_eras hecks_era_texts].each do |table|
80
80
  @db.exec_params("UPDATE #{table} SET domain = $1 WHERE domain = $2", [@domain, @formerly_known_as])
81
81
  end
82
82
  # hecks_attestations is created lazily on first reattest!, so it may not exist.
@@ -6,8 +6,8 @@ module Hecks
6
6
  module Adapters
7
7
  class PostgresEra
8
8
  class Lineage
9
- # CRUD over `hecks_eras`/`hecks_era_texts`/`hecks_approvals`: era text
10
- # integrity, archiving, Layer-3 approvals, and once-only era naming.
9
+ # CRUD over `hecks_eras`/`hecks_era_texts`: era text
10
+ # integrity, archiving, and once-only era naming.
11
11
  module EraStore
12
12
  # Verifies each held text's digest before returning it; a missing digest is backfilled.
13
13
  def eras
@@ -48,26 +48,6 @@ module Hecks
48
48
  )
49
49
  end
50
50
 
51
- # Always inserts a new row; `approval_for` picks the latest for a shape pair.
52
- def record_approval!(from:, to:, edge_digest:)
53
- @db.exec_params(
54
- "INSERT INTO hecks_approvals (domain, from_label, to_label, edge_digest, reviewed_ordinal) " \
55
- "VALUES ($1, $2, $3, $4, $5)",
56
- [@domain, from, to, edge_digest, last_ordinal]
57
- )
58
- end
59
-
60
- def approval_for(from:, to:)
61
- rows = @db.exec_params(
62
- "SELECT edge_digest, reviewed_ordinal FROM hecks_approvals " \
63
- "WHERE domain = $1 AND from_label = $2 AND to_label = $3 ORDER BY approved_at DESC, reviewed_ordinal DESC LIMIT 1",
64
- [@domain, from, to]
65
- )
66
- return nil if rows.ntuples.zero?
67
-
68
- { edge_digest: rows[0]["edge_digest"], reviewed_ordinal: rows[0]["reviewed_ordinal"].to_i }
69
- end
70
-
71
51
  # Sets hash/label only once; nothing ever recomputes a stored name to verify it.
72
52
  def mint_name!(ordinal, hash, label)
73
53
  @db.exec_params(
@@ -118,7 +118,6 @@ module Hecks
118
118
  # NULL reads as 1.
119
119
  @db.exec("ALTER TABLE hecks_eras ADD COLUMN IF NOT EXISTS canon_form int")
120
120
  @db.exec(ERA_TEXTS_SQL)
121
- @db.exec(APPROVALS_SQL)
122
121
  end
123
122
 
124
123
  def create_journal!
@@ -28,8 +28,6 @@ module Hecks
28
28
  include FieldCache
29
29
  include TransformInstaller
30
30
 
31
- JOURNAL_COLUMNS = "ordinal, era, aggregate, aggregate_id, operation, state, mirrors".freeze
32
-
33
31
  # Longer identifiers are silently truncated by Postgres, so `qualified_name` hashes them.
34
32
  POSTGRES_IDENTIFIER_LIMIT = 63
35
33
 
@@ -100,7 +98,15 @@ module Hecks
100
98
  return full if full.bytesize <= POSTGRES_IDENTIFIER_LIMIT
101
99
 
102
100
  digest = Digest::SHA256.hexdigest(full)[0, 8]
103
- "#{full.byteslice(0, POSTGRES_IDENTIFIER_LIMIT - digest.bytesize - 1)}_#{digest}"
101
+ "#{whole_characters(full, POSTGRES_IDENTIFIER_LIMIT - digest.bytesize - 1)}_#{digest}"
102
+ end
103
+
104
+ # The longest prefix of `text` made of whole characters that fits in `limit` bytes, so a
105
+ # multi-byte character is dropped rather than cut in half.
106
+ def whole_characters(text, limit)
107
+ bytes = text.byteslice(0, limit)
108
+ bytes = bytes.byteslice(0, bytes.bytesize - 1) until bytes.valid_encoding?
109
+ bytes
104
110
  end
105
111
 
106
112
  def quote(name) = PG::Connection.quote_ident(name.to_s)
@@ -2,7 +2,6 @@ require_relative "../../../../../../runtime/registry"
2
2
  require_relative "../../storage_shape"
3
3
  require_relative "../../translation/audit"
4
4
  require_relative "../../translation/scaffold"
5
- require_relative "../../translation/approval_file"
6
5
  require_relative "context"
7
6
  require_relative "refusals"
8
7
 
@@ -11,7 +10,7 @@ module Hecks
11
10
  class PostgresEra
12
11
  module LineageManager
13
12
  # Names the source era, resolves its one outgoing translation edge, and mints the
14
- # next era once approval, coverage and the audited chain all check out.
13
+ # next era once coverage and the audited chain check out.
15
14
  module Minter
16
15
  include Refusals
17
16
 
@@ -20,8 +19,7 @@ module Hecks
20
19
  # @param context [Context] the era check so far; `latest` is the latest held era, named
21
20
  # here if it has no name yet
22
21
  # @return [Integer] the ordinal of the era minted
23
- # @raise [Runtime::WiringError] if the edge, its approval, its coverage or its audit
24
- # refuses
22
+ # @raise [Runtime::WiringError] if the edge, its coverage or its audit refuses
25
23
  def mint!(context)
26
24
  ensure_named!(context.lineage, context.latest)
27
25
  context = context.with(latest: context.lineage.eras.last)
@@ -41,29 +39,6 @@ module Hecks
41
39
  edges.first
42
40
  end
43
41
 
44
- # Refuses a mint whose edge carries a compute or rekey rule without an approval.
45
- #
46
- # A compute or rekey's only verification is a human-approved audit sample. Two approvals
47
- # satisfy it: one recorded in the journal that matches the edge and the journal's current
48
- # tip, or a committed `translations/<edge>.approval` that matches the edge's digest and
49
- # records a passed rehearsal on a compatible host release. A committed approval that
50
- # applies is written into the journal, so the journal stays the single history.
51
- def ensure_compute_rekey_approved!(context, edge)
52
- return unless Translation::ApprovalFile.needs_rehearsal?(edge)
53
-
54
- approval = context.lineage.approval_for(from: edge.from, to: edge.to)
55
- digest = Translation::Audit.edge_digest(edge)
56
- tip = context.lineage.last_ordinal
57
- return if approved_at_tip?(approval, digest, tip)
58
-
59
- if Translation::ApprovalFile.applicable(context.directory, edge)
60
- context.lineage.record_approval!(from: edge.from, to: edge.to, edge_digest: digest)
61
- return
62
- end
63
-
64
- refuse_unapproved!(context, edge, approval, tip)
65
- end
66
-
67
42
  # Diffs the held era against the current shape and writes the edge file —
68
43
  # confident rules inline, ambiguities as parse-refusing `unresolved` lines.
69
44
  def scaffold!(_registry, bluebook, lineage, latest, directory)
@@ -104,10 +79,9 @@ module Hecks
104
79
  chain
105
80
  end
106
81
 
107
- # The edge leaving the held era, once resolved, approved and found to cover the diff.
82
+ # The edge leaving the held era, once resolved and found to cover the diff.
108
83
  def verified_edge!(context)
109
84
  edge = resolve_edge!(context)
110
- ensure_compute_rekey_approved!(context, edge)
111
85
  check_coverage!(context.registry, context.bluebook, shadow(context.latest[:held_text]), edge)
112
86
  edge
113
87
  end
@@ -2,8 +2,8 @@ module Hecks
2
2
  module Adapters
3
3
  class PostgresEra
4
4
  module LineageManager
5
- # The refusals a mint raises: toward the scaffold when no edge exists, and for a forked,
6
- # stale or unapproved edge. Each is built from the era check's `Context`.
5
+ # The refusals a mint raises: toward the scaffold when no edge exists, and for a forked or
6
+ # stale edge. Each is built from the era check's `Context`.
7
7
  module Refusals
8
8
  # Refuses toward the authoring loop, or, under HECKS_SCAFFOLD=1, scaffolds the
9
9
  # missing edge first and names the file it wrote.
@@ -37,30 +37,6 @@ module Hecks
37
37
  "re-run hecks scaffold_translation", minting: false)
38
38
  end
39
39
 
40
- # Whether the journal already holds an approval of this edge at the journal's tip.
41
- def approved_at_tip?(approval, digest, tip)
42
- approval && approval[:edge_digest] == digest && approval[:reviewed_ordinal] == tip
43
- end
44
-
45
- def refuse_unapproved!(context, edge, approval, tip)
46
- if approval && approval[:edge_digest] == Translation::Audit.edge_digest(edge)
47
- raise era_refusal(context, "the journal advanced past the approved " \
48
- "review (ordinal #{approval[:reviewed_ordinal]} reviewed, #{tip} now) — the samples a " \
49
- "human approved no longer cover the data; re-run hecks audit_translation with --approve")
50
- end
51
-
52
- mismatch = Translation::ApprovalFile.host_mismatch(context.directory, edge)
53
- raise era_refusal(context, unapproved_reason(mismatch))
54
- end
55
-
56
- def unapproved_reason(mismatch)
57
- return "the committed approval does not apply — #{mismatch}" if mismatch
58
-
59
- "this edge carries a compute or rekey " \
60
- "rule, and the audit's human-approved sample is its only verification — run " \
61
- "hecks audit_translation with --approve, then boot again"
62
- end
63
-
64
40
  # Builds the refusal; a mint's own refusals say "cannot mint era N of X", the
65
41
  # edge-resolution ones say "cannot boot X".
66
42
  def era_refusal(context, text, minting: true)
@@ -9,7 +9,7 @@ module Hecks
9
9
  # Checks one aggregate's records across the edge for id conservation and, where a
10
10
  # per-record comparison is possible, agreement with the reference transform.
11
11
  #
12
- # Compute paths and rekeyed aggregates are exempt from the comparison.
12
+ # Rekeyed aggregates are exempt from the comparison.
13
13
  #
14
14
  # @param violations [Array<String>] collector this method appends messages to
15
15
  # @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
@@ -24,7 +24,7 @@ module Hecks
24
24
  check_id_conservation!(violations, aggregate, before, after, rekeyed: rekeyed)
25
25
 
26
26
  return unless declared
27
- # A rekey's SQL is its only implementation and `after` cannot be looked up by old id.
27
+ # A rekeyed record's `after` cannot be looked up by its old id.
28
28
  return if rekeyed
29
29
 
30
30
  check_value_preservation!(violations, aggregate, declared, before, after)
@@ -56,12 +56,11 @@ module Hecks
56
56
  # @return [void]
57
57
  def check_value_preservation!(violations, aggregate, declared, before, after)
58
58
  rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
59
- compute_paths = compute_paths_of(declared)
60
59
 
61
60
  before.each do |id, state|
62
61
  next unless after.key?(id)
63
62
 
64
- diverged = divergence(rules, id, state, after[id], compute_paths)
63
+ diverged = divergence(rules, id, state, after[id])
65
64
  next if diverged.empty?
66
65
 
67
66
  violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
@@ -75,28 +74,6 @@ module Hecks
75
74
  # @return [Hash{String => Object}, nil] a deep copy with String keys at every depth
76
75
  def normalize(state) = JSON.parse(JSON.generate(state))
77
76
 
78
- # Removes the paths a compute owns from a normalized state, in place.
79
- #
80
- # A dotted path removes only its own member, never the whole top-level key, so sibling
81
- # members stay under the equivalence check and silent loss there is still caught.
82
- #
83
- # @param state [Hash{String => Object}] a normalized state; mutated
84
- # @param paths [Array<String>] bare (`"price_cents"`) or dotted (`"price.cents"`)
85
- # compute paths
86
- # @return [Hash{String => Object}] `state` itself, with those paths deleted
87
- def strip_compute_paths(state, paths)
88
- paths.each do |path|
89
- segments = path.split(".")
90
- if segments.length == 1
91
- state.delete(segments.first)
92
- else
93
- parent = segments[0..-2].reduce(state) { |node, segment| node.is_a?(Hash) ? node[segment] : nil }
94
- parent.delete(segments.last) if parent.is_a?(Hash)
95
- end
96
- end
97
- state
98
- end
99
-
100
77
  private
101
78
 
102
79
  # The message for a rekeying edge whose record count changed, or nil.
@@ -118,15 +95,11 @@ module Hecks
118
95
  "(lost #{lost.sort.inspect}, gained #{gained.sort.inspect})"
119
96
  end
120
97
 
121
- def compute_paths_of(declared)
122
- declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
123
- end
124
-
125
98
  # The keys at which one record's translated state differs from the reference transform.
126
- def divergence(rules, id, state, translated, compute_paths)
99
+ def divergence(rules, id, state, translated)
127
100
  entry = Ports::Persistence::Entry.new(operation: "save", id: id, state: state.transform_keys(&:to_sym))
128
- expected = strip_compute_paths(normalize(rules.translate(entry).state), compute_paths)
129
- actual = strip_compute_paths(normalize(translated), compute_paths)
101
+ expected = normalize(rules.translate(entry).state)
102
+ actual = normalize(translated)
130
103
  (expected.keys | actual.keys).reject { |key| expected[key] == actual[key] }
131
104
  end
132
105
  end
@@ -8,7 +8,7 @@ module Hecks
8
8
  #
9
9
  # @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
10
10
  # @param declared [Bluebook::TranslationAggregate, nil] this edge's rules; the
11
- # destinations of its renames, moves, converts and computes count as fed
11
+ # destinations of its renames, moves, converts and derives count as fed
12
12
  # @param after [Hash{String => Hash}] translated state per record id
13
13
  # @return [Array<String>] names of unfed attributes, in declaration order; `[]` when
14
14
  # every attribute is fed or `after` holds no records
@@ -25,12 +25,12 @@ module Hecks
25
25
  # The top-level attribute names an edge's destinations feed.
26
26
  #
27
27
  # @param declared [Bluebook::TranslationAggregate, nil] this edge's rules
28
- # @return [Array<String>] the destinations of renames, moves, converts and computes, cut to
28
+ # @return [Array<String>] the destinations of renames, moves, converts and derives, cut to
29
29
  # their top-level name; `[]` when there is no edge
30
30
  def fed_names(declared)
31
31
  return [] unless declared
32
32
 
33
- rules = [declared.moves, declared.converts, declared.computes].flat_map { |list| list.map(&:to) }
33
+ rules = [declared.moves, declared.converts, declared.derives].flat_map { |list| list.map(&:to) }
34
34
  (declared.renames.values.map(&:to_s) + rules).map { |path| path.to_s.split(".").first }
35
35
  end
36
36
 
@@ -3,12 +3,11 @@ require "json"
3
3
  require_relative "audit/layer_one"
4
4
  require_relative "audit/layer_two"
5
5
  require_relative "audit/unfed_report"
6
- require_relative "audit/approval_digest"
7
6
 
8
7
  module Hecks
9
8
  module Translation
10
- # Derives the audit's assertions per layer (audit/layer_one, layer_two, unfed_report,
11
- # approval_digest); Layer 3, the human-approved sample, is assembled in `check`.
9
+ # Derives the audit's assertions per layer (audit/layer_one, layer_two, unfed_report);
10
+ # Layer 3, the human-approved sample, is assembled in `check`.
12
11
  module Audit
13
12
  Verdict = Struct.new(:violations, :dropped, :unfed, :samples, keyword_init: true) do
14
13
  # Reports whether the mechanical layers found nothing to refuse.
@@ -23,7 +22,6 @@ module Hecks
23
22
  extend LayerOne
24
23
  extend LayerTwo
25
24
  extend UnfedReport
26
- extend ApprovalDigest
27
25
 
28
26
  module_function
29
27
 
@@ -0,0 +1,52 @@
1
+ module Hecks
2
+ module Translation
3
+ # Compiles a `Bluebook::TranslationExpression` tree into a Postgres text expression over a
4
+ # jsonb record. The compiled form yields NULL wherever the expression has no value, and
5
+ # agrees with `TranslationExpression.evaluate` on every record.
6
+ module ExpressionSql
7
+ UPPER = ("A".."Z").to_a.join.freeze
8
+ LOWER = UPPER.downcase.freeze
9
+
10
+ module_function
11
+
12
+ # @param tree [Array] a tree `Bluebook::TranslationExpression.parse` returned
13
+ # @param record [String] the SQL that names the jsonb record the expression reads
14
+ # @return [String] a SQL expression of type text
15
+ def compile(tree, record)
16
+ case tree.first
17
+ when "lit" then text(tree[1])
18
+ when "null" then "NULL::text"
19
+ when "ref" then reference(tree[1], record)
20
+ else function(tree.first, tree.drop(1), record)
21
+ end
22
+ end
23
+
24
+ def function(name, arguments, record)
25
+ return lookup(arguments, record) if name == "lookup"
26
+
27
+ parts = arguments.map { |argument| compile(argument, record) }
28
+ case name
29
+ when "lower" then "translate(#{parts.first}, #{text(UPPER)}, #{text(LOWER)})"
30
+ when "upper" then "translate(#{parts.first}, #{text(LOWER)}, #{text(UPPER)})"
31
+ when "concat" then "(#{parts.join(" || ")})"
32
+ else "replace(#{parts.join(", ")})"
33
+ end
34
+ end
35
+
36
+ # Only text counts as a value: a number, object, array or null reads as no value.
37
+ def reference(path, record)
38
+ at = RuleCompiler.path_literal(path)
39
+ "(CASE WHEN jsonb_typeof(#{record} #> #{at}) = 'string' THEN #{record} #>> #{at} END)"
40
+ end
41
+
42
+ def lookup(arguments, record)
43
+ key, table, fallback = arguments
44
+ branches = table.map { |from, to| "WHEN #{text(from)} THEN #{text(to)}" }.join(" ")
45
+ "(SELECT CASE WHEN __v IS NULL THEN NULL ELSE CASE __v #{branches} ELSE #{compile(fallback, record)} END END " \
46
+ "FROM (SELECT #{compile(key, record)} AS __v) __lookup)"
47
+ end
48
+
49
+ def text(value) = "#{RuleCompiler.text_literal(value)}::text"
50
+ end
51
+ end
52
+ end