hecks 3.14.0 → 5.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.
- checksums.yaml +4 -4
- data/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/exe/hecks +1 -1
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +9 -16
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/app/responses.rb +3 -1
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value.rb +10 -21
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
- data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +306 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +151 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +574 -229
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +1037 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +555 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -516
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +22 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +244 -114
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +244 -148
- data/rust/src/kernel/mod.rs +47 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/present.rs +261 -0
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/rust/host/src/approval.rs +0 -652
data/rust/host/src/journal.rs
CHANGED
|
@@ -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
|
-
&[
|
|
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>(
|
|
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(
|
|
167
|
+
.query(
|
|
168
|
+
"SELECT ordinal, seed, sagas_backfilled FROM hecks_lambda_snapshot",
|
|
169
|
+
&[],
|
|
170
|
+
)
|
|
158
171
|
.await?;
|
|
159
|
-
Ok(rows.first().map(|row| Snapshot {
|
|
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>(
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
|
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(
|
|
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
|
-
&[
|
|
420
|
+
&[
|
|
421
|
+
&era,
|
|
422
|
+
&storage,
|
|
423
|
+
&mutation.id,
|
|
424
|
+
&mutation.operation,
|
|
425
|
+
&mutation.state,
|
|
426
|
+
],
|
|
432
427
|
)
|
|
433
428
|
.await
|
|
434
|
-
.with_context(||
|
|
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(||
|
|
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(
|
|
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
|
|
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!(
|
|
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
|
-
///
|
|
538
|
-
|
|
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(
|
|
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"))
|
|
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!(
|
|
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(
|
|
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 {
|
|
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
|
¤t_era,
|
|
633
|
-
&Mutation {
|
|
618
|
+
&Mutation {
|
|
619
|
+
aggregate: "Account",
|
|
620
|
+
id: "current-era",
|
|
621
|
+
operation: "save",
|
|
622
|
+
state: &state,
|
|
623
|
+
},
|
|
634
624
|
)
|
|
635
625
|
.await;
|
|
636
|
-
assert!(
|
|
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 {
|
|
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 {
|
|
640
|
+
&Mutation {
|
|
641
|
+
aggregate: "Account",
|
|
642
|
+
id: "stale-era",
|
|
643
|
+
operation: "save",
|
|
644
|
+
state: &state,
|
|
645
|
+
},
|
|
644
646
|
)
|
|
645
647
|
.await;
|
|
646
|
-
assert!(
|
|
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
|
|
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
|
|
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"))
|
|
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
|
|
674
|
+
.expect("run mint_and_seed_lineage.rb -- is `ruby` on PATH?");
|
|
671
675
|
assert!(
|
|
672
676
|
output.status.success(),
|
|
673
|
-
"
|
|
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
|
|
678
|
-
//
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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, "
|
|
688
|
-
assert_eq!(
|
|
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!(
|
|
701
|
+
assert_ne!(
|
|
702
|
+
from_label, to_label,
|
|
703
|
+
"a real edge names two DIFFERENT shapes"
|
|
704
|
+
);
|
|
694
705
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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) =
|
|
717
|
-
|
|
718
|
-
|
|
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!(
|
|
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) =
|
|
772
|
-
|
|
773
|
-
|
|
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
|
|
780
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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")
|
|
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")
|
|
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")
|
|
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
|
-
(
|
|
824
|
-
|
|
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) =
|
|
832
|
-
|
|
833
|
-
|
|
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
|
|
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!(
|
|
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
|
+
}
|