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
@@ -94,7 +94,14 @@ pub async fn record_dead_letter<C: GenericClient>(
94
94
  "INSERT INTO hecks_cross_domain_dead_letters \
95
95
  (policy, target_domain, target_verb, payload, error, attempts) \
96
96
  VALUES ($1, $2, $3, $4, $5, $6)",
97
- &[&policy, &target_domain, &target_verb, payload, &error, &attempts],
97
+ &[
98
+ &policy,
99
+ &target_domain,
100
+ &target_verb,
101
+ payload,
102
+ &error,
103
+ &attempts,
104
+ ],
98
105
  )
99
106
  .await?;
100
107
  Ok(())
@@ -122,7 +129,10 @@ pub async fn load_steps<C: GenericClient>(client: &C) -> anyhow::Result<Vec<serd
122
129
 
123
130
  /// Steps recorded after `ordinal` — what remains to replay on top of a
124
131
  /// snapshot taken as of that ordinal (normally empty).
125
- pub async fn load_steps_after<C: GenericClient>(client: &C, ordinal: i64) -> anyhow::Result<Vec<serde_json::Value>> {
132
+ pub async fn load_steps_after<C: GenericClient>(
133
+ client: &C,
134
+ ordinal: i64,
135
+ ) -> anyhow::Result<Vec<serde_json::Value>> {
126
136
  let rows = client
127
137
  .query(
128
138
  "SELECT verb, args FROM hecks_lambda_journal WHERE ordinal > $1 ORDER BY ordinal",
@@ -154,9 +164,16 @@ pub struct Snapshot {
154
164
  /// before the first accepted command, when there's nothing to seed from yet.
155
165
  pub async fn load_snapshot<C: GenericClient>(client: &C) -> anyhow::Result<Option<Snapshot>> {
156
166
  let rows = client
157
- .query("SELECT ordinal, seed, sagas_backfilled FROM hecks_lambda_snapshot", &[])
167
+ .query(
168
+ "SELECT ordinal, seed, sagas_backfilled FROM hecks_lambda_snapshot",
169
+ &[],
170
+ )
158
171
  .await?;
159
- Ok(rows.first().map(|row| Snapshot { ordinal: row.get(0), seed: row.get(1), sagas_backfilled: row.get(2) }))
172
+ Ok(rows.first().map(|row| Snapshot {
173
+ ordinal: row.get(0),
174
+ seed: row.get(1),
175
+ sagas_backfilled: row.get(2),
176
+ }))
160
177
  }
161
178
 
162
179
  /// Returns the new journal row's ordinal, for `save_snapshot` to pair with it.
@@ -177,7 +194,11 @@ pub async fn append<C: GenericClient>(
177
194
  /// Upserts the one-row snapshot cache at `ordinal`. Always sets
178
195
  /// `sagas_backfilled = true`; only a row written before that column
179
196
  /// existed can ever read back `false`.
180
- pub async fn save_snapshot<C: GenericClient>(client: &C, ordinal: i64, seed: &serde_json::Value) -> anyhow::Result<()> {
197
+ pub async fn save_snapshot<C: GenericClient>(
198
+ client: &C,
199
+ ordinal: i64,
200
+ seed: &serde_json::Value,
201
+ ) -> anyhow::Result<()> {
181
202
  client
182
203
  .execute(
183
204
  "INSERT INTO hecks_lambda_snapshot (id, ordinal, seed, sagas_backfilled) VALUES (true, $1, $2, true) \
@@ -269,66 +290,23 @@ impl LineageConfig {
269
290
  /// Whether `qualified_aggregate` is mirrored into the era-shaped head
270
291
  /// snapshots.
271
292
  pub fn mirrors(&self, qualified_aggregate: &str) -> bool {
272
- match &self.mirrored {
273
- None => true,
274
- Some(capable) => capable.contains(qualified_aggregate),
275
- }
276
- }
277
- }
278
-
279
- /// Ports `Naming.snake` (lib/hecks/naming.rb) verbatim — must match Ruby
280
- /// exactly so both runtimes derive the same storage names.
281
- pub fn snake(name: &str) -> String {
282
- word_boundary(&acronym_boundary(name)).to_ascii_lowercase()
283
- }
284
-
285
- fn acronym_boundary(s: &str) -> String {
286
- let chars: Vec<char> = s.chars().collect();
287
- let mut out = String::new();
288
- let mut i = 0;
289
- while i < chars.len() {
290
- if chars[i].is_ascii_uppercase() {
291
- let mut j = i;
292
- while j < chars.len() && chars[j].is_ascii_uppercase() {
293
- j += 1;
294
- }
295
- let run_len = j - i;
296
- if run_len >= 2 && j < chars.len() && chars[j].is_ascii_lowercase() {
297
- out.extend(&chars[i..j - 1]);
298
- out.push('_');
299
- out.push(chars[j - 1]);
300
- i = j;
301
- continue;
302
- }
303
- out.extend(&chars[i..j]);
304
- i = j;
305
- continue;
306
- }
307
- out.push(chars[i]);
308
- i += 1;
293
+ self.mirrored
294
+ .as_ref()
295
+ .is_none_or(|capable| capable.contains(qualified_aggregate))
309
296
  }
310
- out
311
297
  }
312
298
 
313
- fn word_boundary(s: &str) -> String {
314
- let chars: Vec<char> = s.chars().collect();
315
- let mut out = String::new();
316
- for (idx, &c) in chars.iter().enumerate() {
317
- if idx > 0 && c.is_ascii_uppercase() {
318
- let prev = chars[idx - 1];
319
- if prev.is_ascii_lowercase() || prev.is_ascii_digit() {
320
- out.push('_');
321
- }
322
- }
323
- out.push(c);
324
- }
325
- out
326
- }
299
+ // `Naming.snake` is the one shared port (rust/codegen/src/hecks_naming.rs); the host reaches it
300
+ // through here so both runtimes derive the same storage names.
301
+ pub use crate::hecks_naming::snake;
327
302
 
328
303
  /// `aggregate.storage_name` ported from ir/aggregate.rb — demodulizes a
329
304
  /// qualified name like `"Banking::Customer"` to `"Customer"` before snaking it.
330
305
  fn storage_name(qualified_aggregate: &str) -> String {
331
- let short = qualified_aggregate.rsplit("::").next().unwrap_or(qualified_aggregate);
306
+ let short = qualified_aggregate
307
+ .rsplit("::")
308
+ .next()
309
+ .unwrap_or(qualified_aggregate);
332
310
  snake(short)
333
311
  }
334
312
 
@@ -348,7 +326,7 @@ fn journal_table(domain: &str) -> String {
348
326
  const POSTGRES_IDENTIFIER_LIMIT: usize = 63;
349
327
 
350
328
  /// Same algorithm as `Lineage#qualified_name` (lineage.rb): 63-byte limit,
351
- /// SHA256 suffix, domain-qualified so two domains' storage names can't
329
+ /// SHA256 suffix over the full name, a cut that never splits a character, domain-qualified so two domains' storage names can't
352
330
  /// collide (ADR 0059).
353
331
  pub(crate) fn qualified_name(domain: &str, suffix: &str) -> String {
354
332
  let full = format!("{}_{}", snake(domain), suffix);
@@ -358,12 +336,23 @@ pub(crate) fn qualified_name(domain: &str, suffix: &str) -> String {
358
336
 
359
337
  let digest = format!("{:x}", Sha256::digest(full.as_bytes()));
360
338
  let digest = &digest[0..8];
361
- let keep = POSTGRES_IDENTIFIER_LIMIT - digest.len() - 1;
339
+ let mut keep = POSTGRES_IDENTIFIER_LIMIT - digest.len() - 1;
340
+ // Cut at the longest prefix of whole characters that fits, as the Ruby side does.
341
+ while !full.is_char_boundary(keep) {
342
+ keep -= 1;
343
+ }
362
344
  format!("{}_{}", &full[0..keep], digest)
363
345
  }
364
346
 
365
347
  fn head_snapshot_table(domain: &str, qualified_aggregate: &str, era: i32) -> String {
366
- qualified_name(domain, &format!("{}_head_snapshot_{}", storage_name(qualified_aggregate), era))
348
+ qualified_name(
349
+ domain,
350
+ &format!(
351
+ "{}_head_snapshot_{}",
352
+ storage_name(qualified_aggregate),
353
+ era
354
+ ),
355
+ )
367
356
  }
368
357
 
369
358
  /// The highest era ordinal on file for `domain`, not a bare existence
@@ -428,10 +417,21 @@ pub async fn append_lineage_mutation<C: GenericClient>(
428
417
  VALUES ($1, $2, $3, $4, $5) RETURNING ordinal",
429
418
  quote_ident(&journal)
430
419
  ),
431
- &[&era, &storage, &mutation.id, &mutation.operation, &mutation.state],
420
+ &[
421
+ &era,
422
+ &storage,
423
+ &mutation.id,
424
+ &mutation.operation,
425
+ &mutation.state,
426
+ ],
432
427
  )
433
428
  .await
434
- .with_context(|| format!("journalling {} #{} into {journal} at era {era}", mutation.aggregate, mutation.id))?;
429
+ .with_context(|| {
430
+ format!(
431
+ "journalling {} #{} into {journal} at era {era}",
432
+ mutation.aggregate, mutation.id
433
+ )
434
+ })?;
435
435
  let ordinal: i64 = row.get(0);
436
436
 
437
437
  client
@@ -445,7 +445,12 @@ pub async fn append_lineage_mutation<C: GenericClient>(
445
445
  &[&mutation.id, &ordinal, &mutation.state],
446
446
  )
447
447
  .await
448
- .with_context(|| format!("upserting {} #{} into {snapshot}", mutation.aggregate, mutation.id))?;
448
+ .with_context(|| {
449
+ format!(
450
+ "upserting {} #{} into {snapshot}",
451
+ mutation.aggregate, mutation.id
452
+ )
453
+ })?;
449
454
 
450
455
  Ok(())
451
456
  }
@@ -464,9 +469,18 @@ pub async fn read_lineage_head_all<C: GenericClient>(
464
469
  storage_name: &str,
465
470
  ) -> anyhow::Result<Vec<(String, serde_json::Value)>> {
466
471
  let rows = client
467
- .query(&format!("SELECT id, state FROM {}", quote_ident(&head_view(domain, storage_name))), &[])
472
+ .query(
473
+ &format!(
474
+ "SELECT id, state FROM {}",
475
+ quote_ident(&head_view(domain, storage_name))
476
+ ),
477
+ &[],
478
+ )
468
479
  .await?;
469
- Ok(rows.into_iter().map(|row| (row.get(0), row.get(1))).collect())
480
+ Ok(rows
481
+ .into_iter()
482
+ .map(|row| (row.get(0), row.get(1)))
483
+ .collect())
470
484
  }
471
485
 
472
486
  /// One row by id, already translated to its current shape.
@@ -478,7 +492,10 @@ pub async fn read_lineage_head_by_id<C: GenericClient>(
478
492
  ) -> anyhow::Result<Option<serde_json::Value>> {
479
493
  let row = client
480
494
  .query_opt(
481
- &format!("SELECT state FROM {} WHERE id = $1", quote_ident(&head_view(domain, storage_name))),
495
+ &format!(
496
+ "SELECT state FROM {} WHERE id = $1",
497
+ quote_ident(&head_view(domain, storage_name))
498
+ ),
482
499
  &[&id],
483
500
  )
484
501
  .await?;
@@ -534,57 +551,17 @@ pub async fn held_eras<C: GenericClient>(client: &C, domain: &str) -> anyhow::Re
534
551
  .collect()
535
552
  }
536
553
 
537
- /// A Layer-3 approval, as recorded — mirrors era_store.rb's `approval_for`.
538
- #[derive(Debug, Clone)]
539
- pub struct Approval {
540
- pub edge_digest: String,
541
- pub reviewed_ordinal: i64,
542
- }
543
-
544
- /// The latest approval for one shape-pair edge, if any (latest wins).
545
- pub async fn approval_for<C: GenericClient>(
546
- client: &C,
547
- domain: &str,
548
- from_label: &str,
549
- to_label: &str,
550
- ) -> anyhow::Result<Option<Approval>> {
551
- let row = client
552
- .query_opt(
553
- "SELECT edge_digest, reviewed_ordinal FROM hecks_approvals \
554
- WHERE domain = $1 AND from_label = $2 AND to_label = $3 \
555
- ORDER BY approved_at DESC, reviewed_ordinal DESC LIMIT 1",
556
- &[&domain, &from_label, &to_label],
557
- )
558
- .await?;
559
-
560
- Ok(row.map(|row| Approval { edge_digest: row.get("edge_digest"), reviewed_ordinal: row.get("reviewed_ordinal") }))
561
- }
562
-
563
- /// Writes an approval into the journal, bound to the journal's tip as it stands, as
564
- /// `LineageStore#record_approval!` does; how a committed approval joins the single history.
565
- pub async fn record_approval<C: GenericClient>(
566
- client: &C,
567
- domain: &str,
568
- from_label: &str,
569
- to_label: &str,
570
- edge_digest: &str,
571
- ) -> anyhow::Result<()> {
572
- let tip = last_ordinal(client, domain).await?;
573
- client
574
- .execute(
575
- "INSERT INTO hecks_approvals (domain, from_label, to_label, edge_digest, reviewed_ordinal) \
576
- VALUES ($1, $2, $3, $4, $5)",
577
- &[&domain, &from_label, &to_label, &edge_digest, &tip],
578
- )
579
- .await?;
580
- Ok(())
581
- }
582
-
583
- /// The journal's high-water ordinal — what a fresh approval binds to and
584
- /// what a mint transaction captures as the new era's watermark.
554
+ /// The journal's high-water ordinal — what a mint transaction captures as the
555
+ /// new era's watermark.
585
556
  pub async fn last_ordinal<C: GenericClient>(client: &C, domain: &str) -> anyhow::Result<i64> {
586
557
  let row = client
587
- .query_one(&format!("SELECT COALESCE(max(ordinal), 0) AS o FROM {}", quote_ident(&journal_table(domain))), &[])
558
+ .query_one(
559
+ &format!(
560
+ "SELECT COALESCE(max(ordinal), 0) AS o FROM {}",
561
+ quote_ident(&journal_table(domain))
562
+ ),
563
+ &[],
564
+ )
588
565
  .await?;
589
566
  Ok(row.get("o"))
590
567
  }
@@ -603,7 +580,8 @@ mod lineage_tests {
603
580
  let owner_role = "rust_host_rls_owner";
604
581
  let app_role = "rust_host_rls_app";
605
582
 
606
- let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/mint_stale_era.rb");
583
+ let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
584
+ .join("tests/fixtures/mint_stale_era.rb");
607
585
  let status = std::process::Command::new("ruby")
608
586
  .arg(&script)
609
587
  .arg(db)
@@ -611,12 +589,15 @@ mod lineage_tests {
611
589
  .arg(app_role)
612
590
  .status()
613
591
  .expect("run mint_stale_era.rb -- is `ruby` on PATH?");
614
- assert!(status.success(), "mint_stale_era.rb failed -- see its own stderr above");
592
+ assert!(
593
+ status.success(),
594
+ "mint_stale_era.rb failed -- see its own stderr above"
595
+ );
615
596
 
616
597
  // The fenced app role — the same connection shape a real deployed
617
598
  // rust/host uses (never the table-owning role, which bypasses RLS).
618
599
  let (client, connection) =
619
- tokio_postgres::connect(&crate::test_pg::conninfo_as(&db, &app_role), NoTls)
600
+ tokio_postgres::connect(&crate::test_pg::conninfo_as(db, app_role), NoTls)
620
601
  .await
621
602
  .expect("connect as the app role");
622
603
  tokio::spawn(async move {
@@ -626,24 +607,48 @@ mod lineage_tests {
626
607
  let state = serde_json::json!({ "cents": 100 });
627
608
 
628
609
  // The era this checkout speaks -- allowed.
629
- let current_era = LineageConfig { domain: "Ledger".to_string(), era: Some(2), mirrored: None };
610
+ let current_era = LineageConfig {
611
+ domain: "Ledger".to_string(),
612
+ era: Some(2),
613
+ mirrored: None,
614
+ };
630
615
  let accepted = append_lineage_mutation(
631
616
  &client,
632
617
  &current_era,
633
- &Mutation { aggregate: "Account", id: "current-era", operation: "save", state: &state },
618
+ &Mutation {
619
+ aggregate: "Account",
620
+ id: "current-era",
621
+ operation: "save",
622
+ state: &state,
623
+ },
634
624
  )
635
625
  .await;
636
- assert!(accepted.is_ok(), "writing under the CURRENT era should succeed: {accepted:?}");
626
+ assert!(
627
+ accepted.is_ok(),
628
+ "writing under the CURRENT era should succeed: {accepted:?}"
629
+ );
637
630
 
638
631
  // The superseded era -- refused by Postgres's own RLS policy.
639
- let stale_era = LineageConfig { domain: "Ledger".to_string(), era: Some(1), mirrored: None };
632
+ let stale_era = LineageConfig {
633
+ domain: "Ledger".to_string(),
634
+ era: Some(1),
635
+ mirrored: None,
636
+ };
640
637
  let refused = append_lineage_mutation(
641
638
  &client,
642
639
  &stale_era,
643
- &Mutation { aggregate: "Account", id: "stale-era", operation: "save", state: &state },
640
+ &Mutation {
641
+ aggregate: "Account",
642
+ id: "stale-era",
643
+ operation: "save",
644
+ state: &state,
645
+ },
644
646
  )
645
647
  .await;
646
- assert!(refused.is_err(), "writing under a SUPERSEDED era should be refused");
648
+ assert!(
649
+ refused.is_err(),
650
+ "writing under a SUPERSEDED era should be refused"
651
+ );
647
652
  let message = format!("{:#}", refused.unwrap_err()).to_lowercase();
648
653
  assert!(
649
654
  message.contains("row-level security") || message.contains("row level security"),
@@ -651,60 +656,59 @@ mod lineage_tests {
651
656
  );
652
657
  }
653
658
 
654
- // Proven against a real compute-migrated era Ruby minted, not a
655
- // synthetic fixture, so a real hecks_approvals row is guaranteed
656
- // to exist by the time this test reads it back.
659
+ // Proven against a real two-era lineage Ruby minted, not a synthetic fixture.
657
660
  #[tokio::test]
658
- async fn held_eras_and_approval_for_read_exactly_what_ruby_s_own_mint_wrote() {
661
+ async fn held_eras_read_exactly_what_ruby_s_own_mint_wrote() {
659
662
  let db = "rust_host_era_read_test";
660
663
  let owner_role = "rust_host_era_read_owner";
661
664
  let app_role = "rust_host_era_read_app";
662
665
 
663
- let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/mint_and_seed_lineage_compute.rb");
666
+ let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
667
+ .join("tests/fixtures/mint_and_seed_lineage.rb");
664
668
  let output = std::process::Command::new("ruby")
665
669
  .arg(&script)
666
670
  .arg(db)
667
671
  .arg(owner_role)
668
672
  .arg(app_role)
669
673
  .output()
670
- .expect("run mint_and_seed_lineage_compute.rb -- is `ruby` on PATH?");
674
+ .expect("run mint_and_seed_lineage.rb -- is `ruby` on PATH?");
671
675
  assert!(
672
676
  output.status.success(),
673
- "mint_and_seed_lineage_compute.rb failed:\n{}",
677
+ "mint_and_seed_lineage.rb failed:\n{}",
674
678
  String::from_utf8_lossy(&output.stderr)
675
679
  );
676
680
 
677
- // Owner-authenticated: hecks_eras/hecks_approvals aren't grantable
678
- // to the app role by design — only the mint path reads them, not
679
- // the RLS-fenced data path the test above exists to prove.
680
- let (client, connection) = tokio_postgres::connect(&crate::test_pg::conninfo_as(&db, &owner_role), NoTls)
681
- .await
682
- .expect("connect as owner");
681
+ // Owner-authenticated: hecks_eras isn't grantable to the app role by design -- only the
682
+ // mint path reads it, not the RLS-fenced data path the test above exists to prove.
683
+ let (client, connection) =
684
+ tokio_postgres::connect(&crate::test_pg::conninfo_as(db, owner_role), NoTls)
685
+ .await
686
+ .expect("connect as owner");
683
687
  tokio::spawn(async move {
684
688
  let _ = connection.await;
685
689
  });
686
690
 
687
- let eras = held_eras(&client, "LedgerCompute").await.expect("held_eras");
688
- assert_eq!(eras.len(), 2, "expected exactly the two eras mint_and_seed_lineage_compute.rb mints: {eras:?}");
691
+ let eras = held_eras(&client, "Ledger").await.expect("held_eras");
692
+ assert_eq!(
693
+ eras.len(),
694
+ 2,
695
+ "expected exactly the two eras mint_and_seed_lineage.rb mints: {eras:?}"
696
+ );
689
697
  assert_eq!(eras[0].ordinal, 1);
690
698
  assert_eq!(eras[1].ordinal, 2);
691
699
  let from_label = eras[0].label.clone().expect("era 1 has a minted label");
692
700
  let to_label = eras[1].label.clone().expect("era 2 has a minted label");
693
- assert_ne!(from_label, to_label, "a real compute edge names two DIFFERENT shapes");
701
+ assert_ne!(
702
+ from_label, to_label,
703
+ "a real edge names two DIFFERENT shapes"
704
+ );
694
705
 
695
- let approval = approval_for(&client, "LedgerCompute", &from_label, &to_label)
696
- .await
697
- .expect("approval_for")
698
- .expect("the real approval mint_and_seed_lineage_compute.rb recorded should still be readable");
699
- assert!(!approval.edge_digest.is_empty());
700
- // reviewed_ordinal binds to the journal's high-water mark at
701
- // approval time; era 1's two prior writes mean it must be >= 2.
702
- assert!(approval.reviewed_ordinal >= 2, "reviewed_ordinal should reflect era 1's real writes: {}", approval.reviewed_ordinal);
703
-
704
- // last_ordinal must have advanced past the approval's own
705
- // reviewed_ordinal by the one post-mint write.
706
- let ordinal = last_ordinal(&client, "LedgerCompute").await.expect("last_ordinal");
707
- assert!(ordinal > approval.reviewed_ordinal, "the journal should have advanced since the approval was reviewed: {ordinal} vs {}", approval.reviewed_ordinal);
706
+ // Era 1 took two writes and era 2 one, so the journal's high-water mark is past them.
707
+ let ordinal = last_ordinal(&client, "Ledger").await.expect("last_ordinal");
708
+ assert!(
709
+ ordinal >= 3,
710
+ "last_ordinal should reflect the real writes: {ordinal}"
711
+ );
708
712
  }
709
713
 
710
714
  // The boot-gate case the RLS test doesn't cover: this crate's own
@@ -713,9 +717,10 @@ mod lineage_tests {
713
717
  // a bare existence check can't.
714
718
  #[tokio::test]
715
719
  async fn current_era_tells_unminted_apart_from_stale_and_finds_the_live_ordinal() {
716
- let (client, connection) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
717
- .await
718
- .expect("connect to postgres");
720
+ let (client, connection) =
721
+ tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
722
+ .await
723
+ .expect("connect to postgres");
719
724
  tokio::spawn(async move {
720
725
  let _ = connection.await;
721
726
  });
@@ -730,7 +735,10 @@ mod lineage_tests {
730
735
 
731
736
  // A domain hecks_eras has never heard of at all.
732
737
  let unminted = current_era(&client, "CurrentEraFixture").await.unwrap();
733
- assert_eq!(unminted, None, "a domain with no hecks_eras rows at all has no current era");
738
+ assert_eq!(
739
+ unminted, None,
740
+ "a domain with no hecks_eras rows at all has no current era"
741
+ );
734
742
 
735
743
  // Era 1 minted, then era 2 supersedes it; hecks_eras is
736
744
  // append-only, so both rows remain on file.
@@ -768,16 +776,23 @@ mod lineage_tests {
768
776
  // reproducing Ruby's mint path; that correctness is lineage_spec.rb's job.
769
777
  #[tokio::test]
770
778
  async fn read_lineage_head_reads_any_aggregates_view_generically() {
771
- let (client, connection) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
772
- .await
773
- .expect("connect to postgres");
779
+ let (client, connection) =
780
+ tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
781
+ .await
782
+ .expect("connect to postgres");
774
783
  tokio::spawn(async move {
775
784
  let _ = connection.await;
776
785
  });
777
786
 
778
787
  // Domain-qualified (ADR 0059); snake("Fixtures") == "fixtures".
779
- client.batch_execute("DROP VIEW IF EXISTS fixtures_widget_head").await.unwrap();
780
- client.batch_execute("DROP TABLE IF EXISTS fixtures_widget_head_snapshot_1").await.unwrap();
788
+ client
789
+ .batch_execute("DROP VIEW IF EXISTS fixtures_widget_head")
790
+ .await
791
+ .unwrap();
792
+ client
793
+ .batch_execute("DROP TABLE IF EXISTS fixtures_widget_head_snapshot_1")
794
+ .await
795
+ .unwrap();
781
796
  client
782
797
  .batch_execute(
783
798
  "CREATE TABLE fixtures_widget_head_snapshot_1 (id text PRIMARY KEY, ordinal bigint NOT NULL, state jsonb NOT NULL)",
@@ -789,7 +804,11 @@ mod lineage_tests {
789
804
  .await
790
805
  .unwrap();
791
806
 
792
- let config = LineageConfig { domain: "Fixtures".to_string(), era: Some(1), mirrored: None };
807
+ let config = LineageConfig {
808
+ domain: "Fixtures".to_string(),
809
+ era: Some(1),
810
+ mirrored: None,
811
+ };
793
812
  client
794
813
  .batch_execute("CREATE TABLE IF NOT EXISTS hecks_journal_fixtures (ordinal bigserial PRIMARY KEY, era int NOT NULL, aggregate text NOT NULL, aggregate_id text NOT NULL, operation text NOT NULL, state jsonb)")
795
814
  .await
@@ -797,45 +816,71 @@ mod lineage_tests {
797
816
  append_lineage_mutation(
798
817
  &client,
799
818
  &config,
800
- &Mutation { aggregate: "Widget", id: "widget-1", operation: "save", state: &serde_json::json!({ "name": "Gadget" }) },
819
+ &Mutation {
820
+ aggregate: "Widget",
821
+ id: "widget-1",
822
+ operation: "save",
823
+ state: &serde_json::json!({ "name": "Gadget" }),
824
+ },
801
825
  )
802
826
  .await
803
827
  .unwrap();
804
828
  append_lineage_mutation(
805
829
  &client,
806
830
  &config,
807
- &Mutation { aggregate: "Widget", id: "widget-2", operation: "save", state: &serde_json::json!({ "name": "Gizmo" }) },
831
+ &Mutation {
832
+ aggregate: "Widget",
833
+ id: "widget-2",
834
+ operation: "save",
835
+ state: &serde_json::json!({ "name": "Gizmo" }),
836
+ },
808
837
  )
809
838
  .await
810
839
  .unwrap();
811
840
 
812
- let one = read_lineage_head_by_id(&client, "Fixtures", "widget", "widget-1").await.unwrap();
841
+ let one = read_lineage_head_by_id(&client, "Fixtures", "widget", "widget-1")
842
+ .await
843
+ .unwrap();
813
844
  assert_eq!(one, Some(serde_json::json!({ "name": "Gadget" })));
814
845
 
815
- let missing = read_lineage_head_by_id(&client, "Fixtures", "widget", "widget-nonexistent").await.unwrap();
846
+ let missing = read_lineage_head_by_id(&client, "Fixtures", "widget", "widget-nonexistent")
847
+ .await
848
+ .unwrap();
816
849
  assert_eq!(missing, None);
817
850
 
818
- let mut all = read_lineage_head_all(&client, "Fixtures", "widget").await.unwrap();
851
+ let mut all = read_lineage_head_all(&client, "Fixtures", "widget")
852
+ .await
853
+ .unwrap();
819
854
  all.sort_by(|a, b| a.0.cmp(&b.0));
820
855
  assert_eq!(
821
856
  all,
822
857
  vec![
823
- ("widget-1".to_string(), serde_json::json!({ "name": "Gadget" })),
824
- ("widget-2".to_string(), serde_json::json!({ "name": "Gizmo" })),
858
+ (
859
+ "widget-1".to_string(),
860
+ serde_json::json!({ "name": "Gadget" })
861
+ ),
862
+ (
863
+ "widget-2".to_string(),
864
+ serde_json::json!({ "name": "Gizmo" })
865
+ ),
825
866
  ]
826
867
  );
827
868
  }
828
869
 
829
870
  #[tokio::test]
830
871
  async fn record_dead_letter_writes_a_real_durable_row() {
831
- let (client, connection) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
832
- .await
833
- .expect("connect to postgres");
872
+ let (client, connection) =
873
+ tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
874
+ .await
875
+ .expect("connect to postgres");
834
876
  tokio::spawn(async move {
835
877
  let _ = connection.await;
836
878
  });
837
879
  ensure_schema(&client).await.unwrap();
838
- client.batch_execute("DELETE FROM hecks_cross_domain_dead_letters").await.unwrap();
880
+ client
881
+ .batch_execute("DELETE FROM hecks_cross_domain_dead_letters")
882
+ .await
883
+ .unwrap();
839
884
 
840
885
  record_dead_letter(
841
886
  &client,
@@ -867,8 +912,64 @@ mod lineage_tests {
867
912
  assert_eq!(policy, "ReviewOnFreeze");
868
913
  assert_eq!(target_domain, "Compliance");
869
914
  assert_eq!(target_verb, "Compliance::AccountFreezeReview.Open");
870
- assert_eq!(payload, serde_json::json!({ "number": { "value": "acct-1" } }));
915
+ assert_eq!(
916
+ payload,
917
+ serde_json::json!({ "number": { "value": "acct-1" } })
918
+ );
871
919
  assert_eq!(error, "ResourceNotFoundException: function not found");
872
920
  assert_eq!(attempts, 3);
873
921
  }
874
922
  }
923
+
924
+ #[cfg(test)]
925
+ mod naming_vector_tests {
926
+ use super::*;
927
+ use serde_json::Value;
928
+
929
+ // spec/fixtures/naming_vectors.json is asserted by spec/naming_vectors_spec.rb against
930
+ // Naming.snake and Lineage#qualified_name, so a drift in either runtime fails a test.
931
+ fn vectors() -> Value {
932
+ serde_json::from_str(include_str!("../../../spec/fixtures/naming_vectors.json")).unwrap()
933
+ }
934
+
935
+ fn table(section: &str) -> Vec<Value> {
936
+ let rows = vectors()[section].as_array().unwrap().clone();
937
+ assert!(!rows.is_empty(), "{section} rows went missing");
938
+ rows
939
+ }
940
+
941
+ #[test]
942
+ fn snake_matches_every_shared_row() {
943
+ for row in table("snake") {
944
+ let input = row["input"].as_str().unwrap();
945
+ assert_eq!(snake(input), row["expected"].as_str().unwrap(), "{input:?}");
946
+ }
947
+ }
948
+
949
+ #[test]
950
+ fn storage_name_matches_every_shared_row() {
951
+ for row in table("storage_name") {
952
+ let input = row["input"].as_str().unwrap();
953
+ assert_eq!(
954
+ storage_name(input),
955
+ row["expected"].as_str().unwrap(),
956
+ "{input:?}"
957
+ );
958
+ }
959
+ }
960
+
961
+ #[test]
962
+ fn qualified_name_matches_every_shared_row() {
963
+ for row in table("qualified_name") {
964
+ let domain = row["domain"].as_str().unwrap();
965
+ let suffix = row["suffix"].as_str().unwrap();
966
+ let got = qualified_name(domain, suffix);
967
+ assert_eq!(
968
+ got,
969
+ row["expected"].as_str().unwrap(),
970
+ "{domain:?} {suffix:?}"
971
+ );
972
+ assert!(got.len() <= POSTGRES_IDENTIFIER_LIMIT);
973
+ }
974
+ }
975
+ }