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/ir.rs
CHANGED
|
@@ -17,7 +17,10 @@ pub fn ir() -> Option<&'static Value> {
|
|
|
17
17
|
/// Aggregates bound to a lineage-capable adapter, as `(qualified_name,
|
|
18
18
|
/// storage_name)` — read from `ir.json`'s own `lineage` key.
|
|
19
19
|
pub fn lineage_capable_aggregates(domain_ir: &Value) -> Vec<(String, String)> {
|
|
20
|
-
let domain_name = domain_ir
|
|
20
|
+
let domain_name = domain_ir
|
|
21
|
+
.get("name")
|
|
22
|
+
.and_then(|v| v.as_str())
|
|
23
|
+
.unwrap_or_default();
|
|
21
24
|
domain_ir
|
|
22
25
|
.get("lineage")
|
|
23
26
|
.and_then(|l| l.get("capable_aggregates"))
|
|
@@ -56,12 +59,66 @@ pub fn persistence_adapters(domain_ir: &Value) -> Vec<(String, String)> {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/// The only persistence adapters `rust/host` has a real backend for — see
|
|
59
|
-
/// `refuse_unsupported_persistence_adapters` below.
|
|
62
|
+
/// `refuse_unsupported_persistence_adapters` below. Must list the same names
|
|
63
|
+
/// as [`BACKENDS`] (a test holds them together).
|
|
60
64
|
pub const SUPPORTED_PERSISTENCE_ADAPTERS: &[&str] = &["Postgres", "PostgresEra"];
|
|
61
65
|
|
|
66
|
+
/// What serves a bound adapter inside this host.
|
|
67
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
68
|
+
pub enum Backend {
|
|
69
|
+
/// The one `tokio_postgres` engine (journal tables, era heads).
|
|
70
|
+
TokioPostgres,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// The host's implemented backends, adapter name -> backend. A future backend
|
|
74
|
+
/// is one more row here (and one in [`SUPPORTED_PERSISTENCE_ADAPTERS`]).
|
|
75
|
+
pub const BACKENDS: &[(&str, Backend)] = &[
|
|
76
|
+
("Postgres", Backend::TokioPostgres),
|
|
77
|
+
("PostgresEra", Backend::TokioPostgres),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/// The backend that serves `adapter`, if the host has one.
|
|
81
|
+
pub fn backend_for(adapter: &str) -> Option<Backend> {
|
|
82
|
+
BACKENDS
|
|
83
|
+
.iter()
|
|
84
|
+
.find(|(name, _)| *name == adapter)
|
|
85
|
+
.map(|(_, backend)| *backend)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// The port every persistence binding must be declared against.
|
|
89
|
+
const PERSISTENCE_PORT: &str = "persistence";
|
|
90
|
+
|
|
91
|
+
/// `ir.json`'s own `adapters` key, as `name -> declaration`. `None` when the
|
|
92
|
+
/// key is absent (an older IR); `Some(Err(..))` when present but malformed
|
|
93
|
+
/// (not an array, or an entry without a string `name`).
|
|
94
|
+
pub fn adapter_declarations(
|
|
95
|
+
domain_ir: &Value,
|
|
96
|
+
) -> Option<Result<std::collections::BTreeMap<String, &Value>, String>> {
|
|
97
|
+
let raw = domain_ir.get("adapters")?;
|
|
98
|
+
let Some(entries) = raw.as_array() else {
|
|
99
|
+
return Some(Err(
|
|
100
|
+
"the IR's \"adapters\" key is not an array of adapter declarations".to_string(),
|
|
101
|
+
));
|
|
102
|
+
};
|
|
103
|
+
let mut declared = std::collections::BTreeMap::new();
|
|
104
|
+
for (index, entry) in entries.iter().enumerate() {
|
|
105
|
+
match entry.get("name").and_then(|n| n.as_str()) {
|
|
106
|
+
Some(name) => {
|
|
107
|
+
declared.insert(name.to_string(), entry);
|
|
108
|
+
}
|
|
109
|
+
None => {
|
|
110
|
+
return Some(Err(format!(
|
|
111
|
+
"the IR's \"adapters\" entry {index} has no string \"name\""
|
|
112
|
+
)))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
Some(Ok(declared))
|
|
117
|
+
}
|
|
118
|
+
|
|
62
119
|
/// The chapter this domain's role checks resolve against, read from
|
|
63
120
|
/// `ir.json`'s own `authorization` key. `None` when nothing provides it.
|
|
64
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
121
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
65
122
|
pub struct AuthorizationProvider {
|
|
66
123
|
/// Qualified grant command, e.g. `Governance::RoleAssignment.Assign`.
|
|
67
124
|
pub grant: String,
|
|
@@ -80,7 +137,7 @@ pub fn authorization_provider(domain_ir: &Value) -> Option<AuthorizationProvider
|
|
|
80
137
|
|
|
81
138
|
/// The chapter that answers "who may sign in", read from `ir.json`'s own
|
|
82
139
|
/// `membership` key. `None` when nothing provides `"membership"`.
|
|
83
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
140
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
84
141
|
pub struct MembershipProvider {
|
|
85
142
|
/// Chapter name, e.g. `Membership`.
|
|
86
143
|
pub provider: String,
|
|
@@ -91,8 +148,10 @@ pub struct MembershipProvider {
|
|
|
91
148
|
/// Aggregates whose mutations mirror into their era head: the lineage-capable
|
|
92
149
|
/// ones plus the membership aggregate, which is not itself lineage-capable.
|
|
93
150
|
pub fn mirrored_aggregates(domain_ir: &Value) -> std::collections::BTreeSet<String> {
|
|
94
|
-
let mut mirrored: std::collections::BTreeSet<String> =
|
|
95
|
-
|
|
151
|
+
let mut mirrored: std::collections::BTreeSet<String> = lineage_capable_aggregates(domain_ir)
|
|
152
|
+
.into_iter()
|
|
153
|
+
.map(|(qualified, _)| qualified)
|
|
154
|
+
.collect();
|
|
96
155
|
if let Some(membership) = membership_provider(domain_ir) {
|
|
97
156
|
mirrored.insert(membership.aggregate);
|
|
98
157
|
}
|
|
@@ -110,7 +169,7 @@ pub fn membership_provider(domain_ir: &Value) -> Option<MembershipProvider> {
|
|
|
110
169
|
/// The chapter that answers "who is this authenticated pair", read from
|
|
111
170
|
/// `ir.json`'s own `identity` key. `None` when nothing provides `"identity"`.
|
|
112
171
|
/// Link's reference field must stay `identity` — never `identity_id`, never `to:`.
|
|
113
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
172
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
114
173
|
pub struct IdentityProvider {
|
|
115
174
|
/// Chapter name, e.g. `Identity`.
|
|
116
175
|
pub provider: String,
|
|
@@ -132,12 +191,117 @@ pub fn identity_provider(domain_ir: &Value) -> Option<IdentityProvider> {
|
|
|
132
191
|
})
|
|
133
192
|
}
|
|
134
193
|
|
|
194
|
+
/// Settings a world binding carries that are not adapter fields.
|
|
195
|
+
const WORLD_EXEMPT_SETTINGS: &[&str] = &["adapter", "role"];
|
|
196
|
+
|
|
197
|
+
fn world_error(what: &str) -> String {
|
|
198
|
+
format!("the IR's \"world\" {what}")
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fn declared_names(declaration: &Value) -> std::collections::BTreeSet<&str> {
|
|
202
|
+
["fields", "secrets"]
|
|
203
|
+
.iter()
|
|
204
|
+
.filter_map(|key| declaration.get(key)?.as_array())
|
|
205
|
+
.flatten()
|
|
206
|
+
.filter_map(|name| name.as_str())
|
|
207
|
+
.collect()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn check_world_binding(
|
|
211
|
+
binding: &Value,
|
|
212
|
+
declared: &std::collections::BTreeMap<String, &Value>,
|
|
213
|
+
) -> Result<(), String> {
|
|
214
|
+
let adapter = binding
|
|
215
|
+
.get("adapter")
|
|
216
|
+
.and_then(|a| a.as_str())
|
|
217
|
+
.ok_or_else(|| world_error("persistence entry has no string \"adapter\""))?;
|
|
218
|
+
let settings = binding
|
|
219
|
+
.get("settings")
|
|
220
|
+
.and_then(|s| s.as_array())
|
|
221
|
+
.ok_or_else(|| world_error("persistence entry's \"settings\" is not an array"))?;
|
|
222
|
+
let Some(declaration) = declared.get(adapter) else {
|
|
223
|
+
return Ok(());
|
|
224
|
+
};
|
|
225
|
+
let known = declared_names(declaration);
|
|
226
|
+
for setting in settings {
|
|
227
|
+
let name = setting
|
|
228
|
+
.as_str()
|
|
229
|
+
.ok_or_else(|| world_error("persistence entry has a setting that is not a string"))?;
|
|
230
|
+
if !WORLD_EXEMPT_SETTINGS.contains(&name) && !known.contains(name) {
|
|
231
|
+
return Err(format!(
|
|
232
|
+
"{adapter} does not declare {name:?} — the world sets it, but the adapter declares \
|
|
233
|
+
{known:?}. Add the field to the adapter, or remove it from the world."
|
|
234
|
+
));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
Ok(())
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Refuses a world that sets, on a declared persistence adapter, a setting the adapter does not
|
|
241
|
+
/// declare as a field or secret (`adapter` and `role` are exempt). Entries for adapters the IR's
|
|
242
|
+
/// `adapters` key does not declare are skipped, and an IR without a `world` key is not checked.
|
|
243
|
+
pub fn refuse_undeclared_world_settings(domain_ir: &Value) -> Result<(), String> {
|
|
244
|
+
let Some(world) = domain_ir.get("world") else {
|
|
245
|
+
return Ok(());
|
|
246
|
+
};
|
|
247
|
+
if !world.is_object() {
|
|
248
|
+
return Err(world_error("key is not an object"));
|
|
249
|
+
}
|
|
250
|
+
let entries = world
|
|
251
|
+
.get("persistence")
|
|
252
|
+
.and_then(|p| p.as_array())
|
|
253
|
+
.ok_or_else(|| world_error("\"persistence\" is not an array"))?;
|
|
254
|
+
let declared = match adapter_declarations(domain_ir) {
|
|
255
|
+
Some(declared) => declared?,
|
|
256
|
+
None => return Ok(()),
|
|
257
|
+
};
|
|
258
|
+
entries
|
|
259
|
+
.iter()
|
|
260
|
+
.try_for_each(|binding| check_world_binding(binding, &declared))
|
|
261
|
+
}
|
|
262
|
+
|
|
135
263
|
/// Refuses at boot rather than silently building a second, disjoint history
|
|
136
264
|
/// nothing but this runtime reads while the real state lives elsewhere.
|
|
265
|
+
///
|
|
266
|
+
/// Each aggregate's bound adapter is looked up in the IR, then in the host's
|
|
267
|
+
/// [`BACKENDS`] table. Rules, in order:
|
|
268
|
+
/// 1. When the IR carries an `adapters` key (even empty), the bound adapter
|
|
269
|
+
/// must be declared there.
|
|
270
|
+
/// 2. That declaration's `port`, when non-null, must be `"persistence"`.
|
|
271
|
+
/// 3. The adapter must have a host backend (the long-standing refusal).
|
|
272
|
+
///
|
|
273
|
+
/// Compatibility: deployed IR files predate the `adapters` key. When it is
|
|
274
|
+
/// absent, rules 1 and 2 are skipped and only rule 3 applies, exactly as
|
|
275
|
+
/// before. A present-but-malformed `adapters` value is refused with a
|
|
276
|
+
/// message naming the problem (it never panics).
|
|
137
277
|
pub fn refuse_unsupported_persistence_adapters(domain_ir: &Value) -> Result<(), String> {
|
|
138
|
-
|
|
278
|
+
refuse_undeclared_world_settings(domain_ir)?;
|
|
279
|
+
let bindings = persistence_adapters(domain_ir);
|
|
280
|
+
if let Some(declared) = adapter_declarations(domain_ir) {
|
|
281
|
+
let declared = declared?;
|
|
282
|
+
for (aggregate, adapter) in &bindings {
|
|
283
|
+
let Some(declaration) = declared.get(adapter) else {
|
|
284
|
+
return Err(format!(
|
|
285
|
+
"{aggregate} binds {adapter}, which the domain's IR does not declare (no \
|
|
286
|
+
.adapter file declares it) — refusing instead of guessing what it means"
|
|
287
|
+
));
|
|
288
|
+
};
|
|
289
|
+
match declaration.get("port") {
|
|
290
|
+
None | Some(Value::Null) => {}
|
|
291
|
+
Some(Value::String(port)) if port == PERSISTENCE_PORT => {}
|
|
292
|
+
Some(port) => {
|
|
293
|
+
return Err(format!(
|
|
294
|
+
"{aggregate} binds {adapter}, which the domain's IR declares for port \
|
|
295
|
+
{port} rather than {PERSISTENCE_PORT:?} — refusing a persistence \
|
|
296
|
+
binding to a non-persistence adapter"
|
|
297
|
+
))
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
let unsupported: Vec<String> = bindings
|
|
139
303
|
.into_iter()
|
|
140
|
-
.filter(|(_, adapter)|
|
|
304
|
+
.filter(|(_, adapter)| backend_for(adapter).is_none())
|
|
141
305
|
.map(|(name, adapter)| format!("{name} (persisted_by {adapter:?})"))
|
|
142
306
|
.collect();
|
|
143
307
|
if unsupported.is_empty() {
|
|
@@ -175,8 +339,10 @@ mod tests {
|
|
|
175
339
|
"lineage": { "capable_aggregates": [{ "name": "Order", "storage_name": "order" }] },
|
|
176
340
|
"membership": { "provider": "Membership", "aggregate": "Membership::Person" }
|
|
177
341
|
});
|
|
178
|
-
let expected: std::collections::BTreeSet<String> =
|
|
179
|
-
|
|
342
|
+
let expected: std::collections::BTreeSet<String> = ["Shop::Order", "Membership::Person"]
|
|
343
|
+
.into_iter()
|
|
344
|
+
.map(String::from)
|
|
345
|
+
.collect();
|
|
180
346
|
assert_eq!(mirrored_aggregates(&ir), expected);
|
|
181
347
|
}
|
|
182
348
|
|
|
@@ -186,7 +352,8 @@ mod tests {
|
|
|
186
352
|
"name": "Shop",
|
|
187
353
|
"lineage": { "capable_aggregates": [{ "name": "Order", "storage_name": "order" }] }
|
|
188
354
|
});
|
|
189
|
-
let expected: std::collections::BTreeSet<String> =
|
|
355
|
+
let expected: std::collections::BTreeSet<String> =
|
|
356
|
+
std::iter::once("Shop::Order").map(String::from).collect();
|
|
190
357
|
assert_eq!(mirrored_aggregates(&ir), expected);
|
|
191
358
|
assert!(mirrored_aggregates(&serde_json::json!({ "name": "Banking" })).is_empty());
|
|
192
359
|
}
|
|
@@ -194,7 +361,10 @@ mod tests {
|
|
|
194
361
|
#[test]
|
|
195
362
|
fn lineage_capable_aggregates_is_empty_for_a_domain_with_no_lineage_key() {
|
|
196
363
|
let ir = serde_json::json!({ "name": "Banking" });
|
|
197
|
-
assert_eq!(
|
|
364
|
+
assert_eq!(
|
|
365
|
+
lineage_capable_aggregates(&ir),
|
|
366
|
+
Vec::<(String, String)>::new()
|
|
367
|
+
);
|
|
198
368
|
}
|
|
199
369
|
|
|
200
370
|
#[test]
|
|
@@ -241,8 +411,63 @@ mod tests {
|
|
|
241
411
|
] }
|
|
242
412
|
});
|
|
243
413
|
let err = refuse_unsupported_persistence_adapters(&ir).unwrap_err();
|
|
244
|
-
assert!(
|
|
245
|
-
|
|
414
|
+
assert!(
|
|
415
|
+
err.contains("Account"),
|
|
416
|
+
"error should name the offending aggregate: {err}"
|
|
417
|
+
);
|
|
418
|
+
assert!(
|
|
419
|
+
err.contains("Heki"),
|
|
420
|
+
"error should name the unsupported adapter: {err}"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fn world_ir(settings: &Value) -> Value {
|
|
425
|
+
serde_json::json!({
|
|
426
|
+
"adapters": [{"name": "PostgresEra", "port": "persistence",
|
|
427
|
+
"fields": ["url"], "secrets": ["password"], "defaults": {}}],
|
|
428
|
+
"world": {"default_adapter": null, "default_database": false,
|
|
429
|
+
"persistence": [{"adapter": "PostgresEra", "settings": settings}]}
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
#[test]
|
|
434
|
+
fn a_world_setting_the_adapter_does_not_declare_is_refused() {
|
|
435
|
+
let err =
|
|
436
|
+
refuse_undeclared_world_settings(&world_ir(&serde_json::json!(["bogus"]))).unwrap_err();
|
|
437
|
+
assert!(
|
|
438
|
+
err.contains("PostgresEra") && err.contains("bogus"),
|
|
439
|
+
"{err}"
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
#[test]
|
|
444
|
+
fn declared_fields_secrets_role_and_adapter_pass() {
|
|
445
|
+
let ir = world_ir(&serde_json::json!(["url", "password", "role", "adapter"]));
|
|
446
|
+
assert!(refuse_undeclared_world_settings(&ir).is_ok());
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#[test]
|
|
450
|
+
fn an_undeclared_adapter_in_the_world_is_skipped() {
|
|
451
|
+
let mut ir = world_ir(&serde_json::json!(["bogus"]));
|
|
452
|
+
ir["adapters"] = serde_json::json!([]);
|
|
453
|
+
assert!(refuse_undeclared_world_settings(&ir).is_ok());
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#[test]
|
|
457
|
+
fn an_ir_without_a_world_key_is_not_checked() {
|
|
458
|
+
assert!(refuse_undeclared_world_settings(&serde_json::json!({"name": "X"})).is_ok());
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
#[test]
|
|
462
|
+
fn a_malformed_world_is_refused() {
|
|
463
|
+
let mut not_object = world_ir(&serde_json::json!([]));
|
|
464
|
+
not_object["world"] = serde_json::json!("x");
|
|
465
|
+
let mut not_array = world_ir(&serde_json::json!([]));
|
|
466
|
+
not_array["world"]["persistence"] = serde_json::json!({});
|
|
467
|
+
let bad_setting = world_ir(&serde_json::json!([3]));
|
|
468
|
+
for ir in [not_object, not_array, bad_setting] {
|
|
469
|
+
assert!(refuse_undeclared_world_settings(&ir).is_err());
|
|
470
|
+
}
|
|
246
471
|
}
|
|
247
472
|
|
|
248
473
|
#[test]
|
|
@@ -276,12 +501,97 @@ mod tests {
|
|
|
276
501
|
assert!(identity_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
277
502
|
}
|
|
278
503
|
|
|
279
|
-
|
|
280
504
|
#[test]
|
|
281
505
|
fn refuse_unsupported_persistence_adapters_passes_a_domain_with_no_persistence_key() {
|
|
282
506
|
let ir = serde_json::json!({ "name": "Pizzas" });
|
|
283
507
|
assert!(refuse_unsupported_persistence_adapters(&ir).is_ok());
|
|
284
508
|
}
|
|
509
|
+
|
|
510
|
+
fn bound(adapters: &[(&str, &str)]) -> Value {
|
|
511
|
+
let aggregates: Vec<Value> = adapters
|
|
512
|
+
.iter()
|
|
513
|
+
.map(|(n, a)| serde_json::json!({"name": n, "adapter": a}))
|
|
514
|
+
.collect();
|
|
515
|
+
serde_json::json!({"persistence": {"aggregates": aggregates}})
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
fn with_adapters(mut ir: Value, adapters: Value) -> Value {
|
|
519
|
+
ir["adapters"] = adapters;
|
|
520
|
+
ir
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#[test]
|
|
524
|
+
fn backend_table_matches_the_supported_names() {
|
|
525
|
+
let names: Vec<&str> = BACKENDS.iter().map(|(n, _)| *n).collect();
|
|
526
|
+
assert_eq!(names, SUPPORTED_PERSISTENCE_ADAPTERS);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
#[test]
|
|
530
|
+
fn absent_adapters_key_keeps_the_old_behaviour() {
|
|
531
|
+
assert!(refuse_unsupported_persistence_adapters(&bound(&[("A", "Postgres")])).is_ok());
|
|
532
|
+
let err = refuse_unsupported_persistence_adapters(&bound(&[("A", "Heki")])).unwrap_err();
|
|
533
|
+
assert!(err.starts_with("rust/host only has a backend"), "{err}");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
#[test]
|
|
537
|
+
fn declared_and_supported_boots_with_null_or_persistence_port() {
|
|
538
|
+
let ir = with_adapters(
|
|
539
|
+
bound(&[("A", "Postgres"), ("B", "PostgresEra")]),
|
|
540
|
+
serde_json::json!([
|
|
541
|
+
{"name": "Postgres", "port": "persistence"},
|
|
542
|
+
{"name": "PostgresEra", "port": null}
|
|
543
|
+
]),
|
|
544
|
+
);
|
|
545
|
+
assert!(refuse_unsupported_persistence_adapters(&ir).is_ok());
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
#[test]
|
|
549
|
+
fn empty_adapters_key_refuses_every_binding_as_undeclared() {
|
|
550
|
+
let ir = with_adapters(bound(&[("A", "Postgres")]), serde_json::json!([]));
|
|
551
|
+
let err = refuse_unsupported_persistence_adapters(&ir).unwrap_err();
|
|
552
|
+
assert!(
|
|
553
|
+
err.contains("A binds Postgres, which the domain's IR does not declare"),
|
|
554
|
+
"{err}"
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#[test]
|
|
559
|
+
fn declared_but_unsupported_hits_the_backend_rule() {
|
|
560
|
+
let ir = with_adapters(
|
|
561
|
+
bound(&[("A", "Heki")]),
|
|
562
|
+
serde_json::json!([{"name": "Heki", "port": "persistence"}]),
|
|
563
|
+
);
|
|
564
|
+
let err = refuse_unsupported_persistence_adapters(&ir).unwrap_err();
|
|
565
|
+
assert!(err.starts_with("rust/host only has a backend"), "{err}");
|
|
566
|
+
assert!(err.contains("A (persisted_by \"Heki\")"), "{err}");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
#[test]
|
|
570
|
+
fn wrong_port_is_refused_before_the_backend_rule() {
|
|
571
|
+
let ir = with_adapters(
|
|
572
|
+
bound(&[("A", "Postgres")]),
|
|
573
|
+
serde_json::json!([{"name": "Postgres", "port": "mail"}]),
|
|
574
|
+
);
|
|
575
|
+
let err = refuse_unsupported_persistence_adapters(&ir).unwrap_err();
|
|
576
|
+
assert!(
|
|
577
|
+
err.contains("A binds Postgres") && err.contains("\"mail\""),
|
|
578
|
+
"{err}"
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
#[test]
|
|
583
|
+
fn malformed_adapters_are_refused_not_panicked() {
|
|
584
|
+
for bad in [
|
|
585
|
+
serde_json::json!("x"),
|
|
586
|
+
serde_json::json!({"name": "Postgres"}),
|
|
587
|
+
serde_json::json!([{"port": "persistence"}]),
|
|
588
|
+
serde_json::json!([null, 3]),
|
|
589
|
+
] {
|
|
590
|
+
let ir = with_adapters(bound(&[("A", "Postgres")]), bad);
|
|
591
|
+
let err = refuse_unsupported_persistence_adapters(&ir).unwrap_err();
|
|
592
|
+
assert!(err.contains("\"adapters\""), "{err}");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
285
595
|
}
|
|
286
596
|
|
|
287
597
|
#[cfg(test)]
|