hecks 3.14.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.
- 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/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- 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/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/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/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/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.rb +13 -0
- 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/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.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 +300 -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 +145 -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/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- 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 +990 -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 +483 -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 +229 -112
- 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 +230 -148
- data/rust/src/kernel/mod.rs +46 -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/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 +82 -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
|
@@ -22,7 +22,11 @@ const OVERVIEW: &str = "overview";
|
|
|
22
22
|
/// The whole config, as `PresentationConfig.load` returns it. A domain
|
|
23
23
|
/// with no ConsoleSettings rows, or none of its relations, reads back
|
|
24
24
|
/// an empty section rather than an error.
|
|
25
|
-
pub async fn load(
|
|
25
|
+
pub async fn load(
|
|
26
|
+
client: &Mutex<Client>,
|
|
27
|
+
wasm_path: &Path,
|
|
28
|
+
config: &LineageConfig,
|
|
29
|
+
) -> anyhow::Result<Value> {
|
|
26
30
|
if let Some(rows) = kernel_rows(client, wasm_path).await? {
|
|
27
31
|
return Ok(reshape(&rows.states, &rows.collections, &rows.overview));
|
|
28
32
|
}
|
|
@@ -31,7 +35,10 @@ pub async fn load(client: &Mutex<Client>, wasm_path: &Path, config: &LineageConf
|
|
|
31
35
|
|
|
32
36
|
/// The Ruby-written relations, split out so the kernel path above reads
|
|
33
37
|
/// as the one decision it is, and so tests can drive this half directly.
|
|
34
|
-
async fn load_from_relations(
|
|
38
|
+
async fn load_from_relations(
|
|
39
|
+
client: &Mutex<Client>,
|
|
40
|
+
config: &LineageConfig,
|
|
41
|
+
) -> anyhow::Result<Value> {
|
|
35
42
|
let guard = client.lock().await;
|
|
36
43
|
let states = read_head_states(&*guard, &config.domain, STATE_STYLE).await?;
|
|
37
44
|
let collections = read_head_states(&*guard, &config.domain, COLLECTION).await?;
|
|
@@ -49,7 +56,10 @@ async fn load_from_relations(client: &Mutex<Client>, config: &LineageConfig) ->
|
|
|
49
56
|
/// Keeping the plain read a single SELECT (via `dispatch::read`'s
|
|
50
57
|
/// snapshot) matters here: this runs on every first paint, alongside
|
|
51
58
|
/// `/api/me` and `/api/ui-schema`.
|
|
52
|
-
pub(crate) async fn kernel_rows(
|
|
59
|
+
pub(crate) async fn kernel_rows(
|
|
60
|
+
client: &Mutex<Client>,
|
|
61
|
+
wasm_path: &Path,
|
|
62
|
+
) -> anyhow::Result<Option<KernelRows>> {
|
|
53
63
|
let state = dispatch::read(client, wasm_path).await?;
|
|
54
64
|
let rows = bucket(state.get("instances"));
|
|
55
65
|
if !rows.is_empty() {
|
|
@@ -57,8 +67,17 @@ pub(crate) async fn kernel_rows(client: &Mutex<Client>, wasm_path: &Path) -> any
|
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
// Empty either way — ask the kernel which kind of empty this is.
|
|
60
|
-
let probe = dispatch::query(
|
|
61
|
-
|
|
70
|
+
let probe = dispatch::query(
|
|
71
|
+
client,
|
|
72
|
+
wasm_path,
|
|
73
|
+
&format!("{CONFIG_DOMAIN}.Styles"),
|
|
74
|
+
json!({}),
|
|
75
|
+
)
|
|
76
|
+
.await?;
|
|
77
|
+
let answered = probe
|
|
78
|
+
.get("queries")
|
|
79
|
+
.and_then(|q| q.as_array())
|
|
80
|
+
.is_some_and(|q| !q.is_empty());
|
|
62
81
|
if answered {
|
|
63
82
|
Ok(Some(rows))
|
|
64
83
|
} else {
|
|
@@ -69,12 +88,16 @@ pub(crate) async fn kernel_rows(client: &Mutex<Client>, wasm_path: &Path) -> any
|
|
|
69
88
|
/// Every ConsoleSettings record in an instance map, split by aggregate.
|
|
70
89
|
fn bucket(instances: Option<&Value>) -> KernelRows {
|
|
71
90
|
let mut rows = KernelRows::default();
|
|
72
|
-
let Some(instances) = instances.and_then(|i| i.as_object()) else {
|
|
91
|
+
let Some(instances) = instances.and_then(|i| i.as_object()) else {
|
|
92
|
+
return rows;
|
|
93
|
+
};
|
|
73
94
|
for (key, state) in instances {
|
|
74
95
|
// `Domain::Aggregate#id`, split on the first `#` — a StateStyle
|
|
75
96
|
// id like `Agg:state` has none itself but would be lost to a
|
|
76
97
|
// split on the last one. Ids feed `presentation_write`'s declare-vs-set check.
|
|
77
|
-
let Some((qualified, id)) = key.split_once('#') else {
|
|
98
|
+
let Some((qualified, id)) = key.split_once('#') else {
|
|
99
|
+
continue;
|
|
100
|
+
};
|
|
78
101
|
match qualified {
|
|
79
102
|
_ if qualified == format!("{CONFIG_DOMAIN}::StateStyle") => {
|
|
80
103
|
rows.state_ids.insert(id.to_string());
|
|
@@ -84,7 +107,9 @@ fn bucket(instances: Option<&Value>) -> KernelRows {
|
|
|
84
107
|
rows.collection_ids.insert(id.to_string());
|
|
85
108
|
rows.collections.push(state.clone());
|
|
86
109
|
}
|
|
87
|
-
_ if qualified == format!("{CONFIG_DOMAIN}::Overview") =>
|
|
110
|
+
_ if qualified == format!("{CONFIG_DOMAIN}::Overview") => {
|
|
111
|
+
rows.overview.push(state.clone());
|
|
112
|
+
}
|
|
88
113
|
_ => {}
|
|
89
114
|
}
|
|
90
115
|
}
|
|
@@ -106,7 +131,7 @@ pub(crate) struct KernelRows {
|
|
|
106
131
|
impl KernelRows {
|
|
107
132
|
/// No row of any of the three kinds — the one answer that cannot
|
|
108
133
|
/// tell a kernel without this chapter from one nobody has saved to.
|
|
109
|
-
fn is_empty(&self) -> bool {
|
|
134
|
+
const fn is_empty(&self) -> bool {
|
|
110
135
|
self.states.is_empty() && self.collections.is_empty() && self.overview.is_empty()
|
|
111
136
|
}
|
|
112
137
|
|
|
@@ -146,7 +171,10 @@ async fn read_head_states<C: GenericClient>(
|
|
|
146
171
|
continue;
|
|
147
172
|
}
|
|
148
173
|
let rows = client
|
|
149
|
-
.query(
|
|
174
|
+
.query(
|
|
175
|
+
&format!("SELECT state FROM {}", journal::quote_ident(&relation)),
|
|
176
|
+
&[],
|
|
177
|
+
)
|
|
150
178
|
.await?;
|
|
151
179
|
return Ok(rows.iter().map(|row| row.get(0)).collect());
|
|
152
180
|
}
|
|
@@ -170,21 +198,25 @@ fn head_view_candidates(host_domain: &str, storage_name: &str) -> Vec<String> {
|
|
|
170
198
|
/// same as the unqualified SELECT below — never a same-named relation
|
|
171
199
|
/// in some other schema.
|
|
172
200
|
async fn relation_exists<C: GenericClient>(client: &C, relation: &str) -> anyhow::Result<bool> {
|
|
173
|
-
let row = client
|
|
201
|
+
let row = client
|
|
202
|
+
.query_one("SELECT to_regclass($1) IS NOT NULL", &[&relation])
|
|
203
|
+
.await?;
|
|
174
204
|
Ok(row.get(0))
|
|
175
205
|
}
|
|
176
206
|
|
|
177
207
|
/// `PresentationConfig.unwrap` — every scalar here is a single-attribute
|
|
178
208
|
/// value object (`{"value": ...}`); anything else rides through untouched.
|
|
179
209
|
fn unwrap(value: &Value) -> Value {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
210
|
+
value
|
|
211
|
+
.get("value")
|
|
212
|
+
.map_or_else(|| value.clone(), Clone::clone)
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
fn unwrap_str(value: Option<&Value>) -> String {
|
|
187
|
-
value
|
|
216
|
+
value
|
|
217
|
+
.map(unwrap)
|
|
218
|
+
.and_then(|v| v.as_str().map(String::from))
|
|
219
|
+
.unwrap_or_default()
|
|
188
220
|
}
|
|
189
221
|
|
|
190
222
|
/// Ruby truthiness for a stored field: absent and JSON `null` are both
|
|
@@ -198,9 +230,15 @@ fn present(value: Option<&Value>) -> Option<&Value> {
|
|
|
198
230
|
/// merged in without overwriting an explicitly-modeled key. Accepts it
|
|
199
231
|
/// either wrapped (a head field) or bare (a `list_of` element).
|
|
200
232
|
fn merge_extra(entry: &mut Map<String, Value>, extra_json: Option<&Value>) {
|
|
201
|
-
let Some(raw) = present(extra_json).map(unwrap) else {
|
|
202
|
-
|
|
203
|
-
|
|
233
|
+
let Some(raw) = present(extra_json).map(unwrap) else {
|
|
234
|
+
return;
|
|
235
|
+
};
|
|
236
|
+
let Some(text) = raw.as_str().filter(|t| !t.is_empty()) else {
|
|
237
|
+
return;
|
|
238
|
+
};
|
|
239
|
+
let Ok(Value::Object(extra)) = serde_json::from_str::<Value>(text) else {
|
|
240
|
+
return;
|
|
241
|
+
};
|
|
204
242
|
for (key, value) in extra {
|
|
205
243
|
entry.entry(key).or_insert(value);
|
|
206
244
|
}
|
|
@@ -217,7 +255,10 @@ fn reshape_states(rows: &[Value]) -> Value {
|
|
|
217
255
|
entry.insert("tone".to_string(), unwrap(tone));
|
|
218
256
|
}
|
|
219
257
|
if let Some(attention) = present(row.get("attention")) {
|
|
220
|
-
entry.insert(
|
|
258
|
+
entry.insert(
|
|
259
|
+
"attention".to_string(),
|
|
260
|
+
json!(unwrap(attention).as_str() == Some("true")),
|
|
261
|
+
);
|
|
221
262
|
}
|
|
222
263
|
merge_extra(&mut entry, row.get("extra_json"));
|
|
223
264
|
|
|
@@ -258,11 +299,24 @@ fn reshape_collection_row(row: &Value) -> Value {
|
|
|
258
299
|
entry.insert("identity".to_string(), identity);
|
|
259
300
|
}
|
|
260
301
|
|
|
261
|
-
entry.insert(
|
|
302
|
+
entry.insert(
|
|
303
|
+
"columns".to_string(),
|
|
304
|
+
Value::Array(
|
|
305
|
+
elements(row, "columns")
|
|
306
|
+
.iter()
|
|
307
|
+
.map(reshape_column)
|
|
308
|
+
.collect(),
|
|
309
|
+
),
|
|
310
|
+
);
|
|
262
311
|
entry.insert("detail_fields".to_string(), reshape_detail_fields(row));
|
|
263
312
|
entry.insert(
|
|
264
313
|
"preconditions".to_string(),
|
|
265
|
-
Value::Array(
|
|
314
|
+
Value::Array(
|
|
315
|
+
elements(row, "preconditions")
|
|
316
|
+
.iter()
|
|
317
|
+
.map(reshape_precondition)
|
|
318
|
+
.collect(),
|
|
319
|
+
),
|
|
266
320
|
);
|
|
267
321
|
entry.insert("field_formats".to_string(), reshape_field_formats(row));
|
|
268
322
|
merge_extra(&mut entry, row.get("extra_json"));
|
|
@@ -271,14 +325,19 @@ fn reshape_collection_row(row: &Value) -> Value {
|
|
|
271
325
|
|
|
272
326
|
/// `Array(row[:columns])` — a null/absent list is the empty one.
|
|
273
327
|
fn elements<'a>(row: &'a Value, field: &str) -> &'a [Value] {
|
|
274
|
-
row.get(field)
|
|
328
|
+
row.get(field)
|
|
329
|
+
.and_then(|v| v.as_array())
|
|
330
|
+
.map_or(&[][..], std::vec::Vec::as_slice)
|
|
275
331
|
}
|
|
276
332
|
|
|
277
333
|
fn reshape_identity(row: &Value) -> Option<Value> {
|
|
278
334
|
let field = present(row.get("identity_field")).map(unwrap)?;
|
|
279
335
|
let mut identity = Map::new();
|
|
280
336
|
identity.insert("field".to_string(), field);
|
|
281
|
-
identity.insert(
|
|
337
|
+
identity.insert(
|
|
338
|
+
"strategy".to_string(),
|
|
339
|
+
row.get("identity_strategy").map_or(Value::Null, unwrap),
|
|
340
|
+
);
|
|
282
341
|
if let Some(source) = present(row.get("identity_source")) {
|
|
283
342
|
identity.insert("source".to_string(), unwrap(source));
|
|
284
343
|
}
|
|
@@ -293,9 +352,15 @@ fn reshape_identity(row: &Value) -> Option<Value> {
|
|
|
293
352
|
/// wire and comes back as a real boolean, as Ruby's own read does.
|
|
294
353
|
fn reshape_column(column: &Value) -> Value {
|
|
295
354
|
let mut entry = Map::new();
|
|
296
|
-
entry.insert(
|
|
355
|
+
entry.insert(
|
|
356
|
+
"field".to_string(),
|
|
357
|
+
column.get("field").cloned().unwrap_or(Value::Null),
|
|
358
|
+
);
|
|
297
359
|
if let Some(sortable) = present(column.get("sortable")) {
|
|
298
|
-
entry.insert(
|
|
360
|
+
entry.insert(
|
|
361
|
+
"sortable".to_string(),
|
|
362
|
+
json!(unwrap(sortable).as_str() == Some("true")),
|
|
363
|
+
);
|
|
299
364
|
}
|
|
300
365
|
if let Some(sort_default) = present(column.get("sort_default")) {
|
|
301
366
|
entry.insert("sort_default".to_string(), unwrap(sort_default));
|
|
@@ -339,8 +404,14 @@ fn reshape_detail_fields(row: &Value) -> Value {
|
|
|
339
404
|
/// meaning "the target must merely exist", matching Ruby's own hash.
|
|
340
405
|
fn reshape_precondition(precondition: &Value) -> Value {
|
|
341
406
|
let mut entry = Map::new();
|
|
342
|
-
entry.insert(
|
|
343
|
-
|
|
407
|
+
entry.insert(
|
|
408
|
+
"field".to_string(),
|
|
409
|
+
precondition.get("field").cloned().unwrap_or(Value::Null),
|
|
410
|
+
);
|
|
411
|
+
entry.insert(
|
|
412
|
+
"state".to_string(),
|
|
413
|
+
precondition.get("state").cloned().unwrap_or(Value::Null),
|
|
414
|
+
);
|
|
344
415
|
merge_extra(&mut entry, precondition.get("extra_json"));
|
|
345
416
|
Value::Object(entry)
|
|
346
417
|
}
|
|
@@ -348,7 +419,11 @@ fn reshape_precondition(precondition: &Value) -> Value {
|
|
|
348
419
|
fn reshape_field_formats(row: &Value) -> Value {
|
|
349
420
|
let mut formats = Map::new();
|
|
350
421
|
for entry in elements(row, "field_formats") {
|
|
351
|
-
let field = entry
|
|
422
|
+
let field = entry
|
|
423
|
+
.get("field")
|
|
424
|
+
.and_then(|v| v.as_str())
|
|
425
|
+
.unwrap_or_default()
|
|
426
|
+
.to_string();
|
|
352
427
|
formats.insert(field, entry.get("format").cloned().unwrap_or(Value::Null));
|
|
353
428
|
}
|
|
354
429
|
Value::Object(formats)
|
|
@@ -357,7 +432,9 @@ fn reshape_field_formats(row: &Value) -> Value {
|
|
|
357
432
|
/// `reshape_overview` — a singleton: no row reads as `{}`, never
|
|
358
433
|
/// `{"stats": []}`, matching Ruby's "never configured" vs "empty".
|
|
359
434
|
fn reshape_overview(rows: &[Value]) -> Value {
|
|
360
|
-
let Some(row) = rows.first() else {
|
|
435
|
+
let Some(row) = rows.first() else {
|
|
436
|
+
return Value::Object(Map::new());
|
|
437
|
+
};
|
|
361
438
|
let stats: Vec<Value> = elements(row, "stats").iter().map(reshape_stat).collect();
|
|
362
439
|
json!({ "stats": stats })
|
|
363
440
|
}
|
|
@@ -366,15 +443,24 @@ fn reshape_overview(rows: &[Value]) -> Value {
|
|
|
366
443
|
/// (`where_json`) and is parsed back out here, exactly as Ruby does.
|
|
367
444
|
fn reshape_stat(stat: &Value) -> Value {
|
|
368
445
|
let mut entry = Map::new();
|
|
369
|
-
entry.insert(
|
|
370
|
-
|
|
446
|
+
entry.insert(
|
|
447
|
+
"label".to_string(),
|
|
448
|
+
stat.get("label").cloned().unwrap_or(Value::Null),
|
|
449
|
+
);
|
|
450
|
+
entry.insert(
|
|
451
|
+
"collection".to_string(),
|
|
452
|
+
stat.get("collection").cloned().unwrap_or(Value::Null),
|
|
453
|
+
);
|
|
371
454
|
if let Some(as_fold) = present(stat.get("as")) {
|
|
372
455
|
entry.insert("as".to_string(), as_fold.clone());
|
|
373
456
|
}
|
|
374
457
|
if let Some(field) = present(stat.get("field")) {
|
|
375
458
|
entry.insert("field".to_string(), field.clone());
|
|
376
459
|
}
|
|
377
|
-
if let Some(where_json) = present(stat.get("where_json"))
|
|
460
|
+
if let Some(where_json) = present(stat.get("where_json"))
|
|
461
|
+
.and_then(|v| v.as_str())
|
|
462
|
+
.filter(|t| !t.is_empty())
|
|
463
|
+
{
|
|
378
464
|
if let Ok(parsed) = serde_json::from_str::<Value>(where_json) {
|
|
379
465
|
entry.insert("where".to_string(), parsed);
|
|
380
466
|
}
|
|
@@ -436,9 +522,14 @@ mod tests {
|
|
|
436
522
|
|
|
437
523
|
#[test]
|
|
438
524
|
fn attention_stored_as_the_string_false_reads_back_as_the_boolean_false() {
|
|
439
|
-
let rows = vec![
|
|
525
|
+
let rows = vec![
|
|
526
|
+
json!({"agg": {"value": "Client"}, "state": {"value": "active"}, "attention": {"value": "false"}}),
|
|
527
|
+
];
|
|
440
528
|
|
|
441
|
-
assert_eq!(
|
|
529
|
+
assert_eq!(
|
|
530
|
+
reshape_states(&rows),
|
|
531
|
+
json!({"Client": {"active": {"attention": false}}})
|
|
532
|
+
);
|
|
442
533
|
}
|
|
443
534
|
|
|
444
535
|
#[test]
|
|
@@ -496,7 +587,10 @@ mod tests {
|
|
|
496
587
|
// `pad` is not modelled as its own attribute — it rides through
|
|
497
588
|
// the identity rule's own opaque blob and has to land inside
|
|
498
589
|
// the identity entry, not beside it.
|
|
499
|
-
assert_eq!(
|
|
590
|
+
assert_eq!(
|
|
591
|
+
config["Contract"]["identity"],
|
|
592
|
+
json!({"field": "number", "strategy": "sequence", "prefix": "C-", "pad": 3})
|
|
593
|
+
);
|
|
500
594
|
}
|
|
501
595
|
|
|
502
596
|
#[test]
|
|
@@ -506,7 +600,10 @@ mod tests {
|
|
|
506
600
|
"field_formats": [], "preconditions": [{"field": "invoice_id"}]
|
|
507
601
|
})];
|
|
508
602
|
|
|
509
|
-
assert_eq!(
|
|
603
|
+
assert_eq!(
|
|
604
|
+
reshape_collections(&rows)["Payment"]["preconditions"],
|
|
605
|
+
json!([{"field": "invoice_id", "state": null}])
|
|
606
|
+
);
|
|
510
607
|
}
|
|
511
608
|
|
|
512
609
|
#[test]
|
|
@@ -541,7 +638,10 @@ mod tests {
|
|
|
541
638
|
})];
|
|
542
639
|
|
|
543
640
|
let entry = &reshape_collections(&rows)["Invoice"];
|
|
544
|
-
assert_eq!(
|
|
641
|
+
assert_eq!(
|
|
642
|
+
entry["columns"],
|
|
643
|
+
json!([{"field": "number", "sortable": false, "sort_default": "desc", "head": "No."}])
|
|
644
|
+
);
|
|
545
645
|
assert_eq!(entry["field_formats"], json!({"issued_on": "date"}));
|
|
546
646
|
}
|
|
547
647
|
|
|
@@ -569,16 +669,24 @@ mod tests {
|
|
|
569
669
|
#[test]
|
|
570
670
|
fn a_domain_that_has_never_saved_an_overview_reads_back_an_empty_section() {
|
|
571
671
|
assert_eq!(reshape_overview(&[]), json!({}));
|
|
572
|
-
assert_eq!(
|
|
672
|
+
assert_eq!(
|
|
673
|
+
reshape(&[], &[], &[]),
|
|
674
|
+
json!({"states": {}, "collections": {}, "overview": {}})
|
|
675
|
+
);
|
|
573
676
|
}
|
|
574
677
|
|
|
575
678
|
#[test]
|
|
576
679
|
fn an_explicitly_modelled_field_always_beats_the_same_key_in_the_extras_blob() {
|
|
577
|
-
let rows = vec![
|
|
680
|
+
let rows = vec![
|
|
681
|
+
json!({"agg": {"value": "Client"}, "tone": {"value": "good"},
|
|
578
682
|
"extra_json": {"value": "{\"tone\":\"danger\",\"note\":\"n\"}"},
|
|
579
|
-
"state": {"value": "active"}})
|
|
683
|
+
"state": {"value": "active"}}),
|
|
684
|
+
];
|
|
580
685
|
|
|
581
|
-
assert_eq!(
|
|
686
|
+
assert_eq!(
|
|
687
|
+
reshape_states(&rows),
|
|
688
|
+
json!({"Client": {"active": {"tone": "good", "note": "n"}}})
|
|
689
|
+
);
|
|
582
690
|
}
|
|
583
691
|
|
|
584
692
|
// What follows needs a real database: which relation this module
|
|
@@ -587,18 +695,26 @@ mod tests {
|
|
|
587
695
|
// `cargo test`'s parallelism doesn't race two against one table.
|
|
588
696
|
|
|
589
697
|
async fn scratch_db(name: &str) -> Mutex<Client> {
|
|
590
|
-
let (admin, conn) =
|
|
591
|
-
|
|
592
|
-
|
|
698
|
+
let (admin, conn) =
|
|
699
|
+
tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), tokio_postgres::NoTls)
|
|
700
|
+
.await
|
|
701
|
+
.expect("connect to postgres");
|
|
593
702
|
tokio::spawn(async move {
|
|
594
703
|
let _ = conn.await;
|
|
595
704
|
});
|
|
596
|
-
admin
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
705
|
+
admin
|
|
706
|
+
.batch_execute(&format!("DROP DATABASE IF EXISTS {name} WITH (FORCE)"))
|
|
707
|
+
.await
|
|
708
|
+
.unwrap();
|
|
709
|
+
admin
|
|
710
|
+
.batch_execute(&format!("CREATE DATABASE {name}"))
|
|
600
711
|
.await
|
|
601
|
-
.
|
|
712
|
+
.unwrap();
|
|
713
|
+
|
|
714
|
+
let (client, conn) =
|
|
715
|
+
tokio_postgres::connect(&crate::test_pg::conninfo(name), tokio_postgres::NoTls)
|
|
716
|
+
.await
|
|
717
|
+
.expect("connect to scratch db");
|
|
602
718
|
tokio::spawn(async move {
|
|
603
719
|
let _ = conn.await;
|
|
604
720
|
});
|
|
@@ -610,17 +726,26 @@ mod tests {
|
|
|
610
726
|
/// A domain matching none of these fixtures' relations, so lookup
|
|
611
727
|
/// falls through to whatever each test seeds directly.
|
|
612
728
|
fn relations_only() -> LineageConfig {
|
|
613
|
-
LineageConfig {
|
|
729
|
+
LineageConfig {
|
|
730
|
+
domain: "Fixtures".to_string(),
|
|
731
|
+
era: None,
|
|
732
|
+
mirrored: None,
|
|
733
|
+
}
|
|
614
734
|
}
|
|
615
735
|
|
|
616
736
|
async fn seed_head(client: &Mutex<Client>, relation: &str, id: &str, state: Value) {
|
|
617
737
|
let guard = client.lock().await;
|
|
618
738
|
guard
|
|
619
|
-
.batch_execute(&format!(
|
|
739
|
+
.batch_execute(&format!(
|
|
740
|
+
"CREATE TABLE IF NOT EXISTS {relation} (id text primary key, state jsonb)"
|
|
741
|
+
))
|
|
620
742
|
.await
|
|
621
743
|
.unwrap();
|
|
622
744
|
guard
|
|
623
|
-
.execute(
|
|
745
|
+
.execute(
|
|
746
|
+
&format!("INSERT INTO {relation} (id, state) VALUES ($1, $2)"),
|
|
747
|
+
&[&id, &state],
|
|
748
|
+
)
|
|
624
749
|
.await
|
|
625
750
|
.unwrap();
|
|
626
751
|
}
|
|
@@ -631,10 +756,15 @@ mod tests {
|
|
|
631
756
|
|
|
632
757
|
#[tokio::test]
|
|
633
758
|
async fn a_kernel_that_carries_the_chapter_but_holds_no_rows_answers_some_empty() {
|
|
634
|
-
let client =
|
|
635
|
-
|
|
759
|
+
let client =
|
|
760
|
+
crate::dispatch::tests::scratch_db("hecks_host_presentation_chapter_no_rows").await;
|
|
761
|
+
let wasm = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
762
|
+
.join("../dist/checkout_fixture.wasm");
|
|
636
763
|
|
|
637
|
-
let rows = kernel_rows(&client, &wasm)
|
|
764
|
+
let rows = kernel_rows(&client, &wasm)
|
|
765
|
+
.await
|
|
766
|
+
.expect("a read")
|
|
767
|
+
.expect("the chapter is compiled in");
|
|
638
768
|
|
|
639
769
|
assert!(rows.states.is_empty());
|
|
640
770
|
assert!(rows.collections.is_empty());
|
|
@@ -654,9 +784,14 @@ mod tests {
|
|
|
654
784
|
let client = scratch_db("hecks_host_presentation_legacy_relation").await;
|
|
655
785
|
seed_head(&client, "state_style_head", "Client:active", json!({"agg": {"value": "Client"}, "state": {"value": "active"}, "tone": {"value": "good"}})).await;
|
|
656
786
|
|
|
657
|
-
let config = load_from_relations(&client, &relations_only())
|
|
787
|
+
let config = load_from_relations(&client, &relations_only())
|
|
788
|
+
.await
|
|
789
|
+
.expect("a config");
|
|
658
790
|
|
|
659
|
-
assert_eq!(
|
|
791
|
+
assert_eq!(
|
|
792
|
+
config["states"],
|
|
793
|
+
json!({"Client": {"active": {"tone": "good"}}})
|
|
794
|
+
);
|
|
660
795
|
assert_eq!(config["collections"], json!({}));
|
|
661
796
|
assert_eq!(config["overview"], json!({}));
|
|
662
797
|
}
|
|
@@ -673,7 +808,9 @@ mod tests {
|
|
|
673
808
|
)
|
|
674
809
|
.await;
|
|
675
810
|
|
|
676
|
-
let config = load_from_relations(&client, &relations_only())
|
|
811
|
+
let config = load_from_relations(&client, &relations_only())
|
|
812
|
+
.await
|
|
813
|
+
.expect("a config");
|
|
677
814
|
|
|
678
815
|
assert_eq!(config["states"]["Client"]["active"]["tone"], "good");
|
|
679
816
|
}
|
|
@@ -683,16 +820,23 @@ mod tests {
|
|
|
683
820
|
let client = scratch_db("hecks_host_presentation_no_relations").await;
|
|
684
821
|
|
|
685
822
|
assert_eq!(
|
|
686
|
-
load_from_relations(&client, &relations_only())
|
|
823
|
+
load_from_relations(&client, &relations_only())
|
|
824
|
+
.await
|
|
825
|
+
.expect("a config"),
|
|
687
826
|
json!({"states": {}, "collections": {}, "overview": {}})
|
|
688
827
|
);
|
|
689
828
|
}
|
|
690
829
|
|
|
691
830
|
#[test]
|
|
692
831
|
fn a_malformed_extras_blob_is_ignored_rather_than_failing_the_whole_read() {
|
|
693
|
-
let rows = vec![
|
|
694
|
-
|
|
832
|
+
let rows = vec![
|
|
833
|
+
json!({"agg": {"value": "Client"}, "state": {"value": "active"},
|
|
834
|
+
"tone": {"value": "good"}, "extra_json": {"value": "not json at all"}}),
|
|
835
|
+
];
|
|
695
836
|
|
|
696
|
-
assert_eq!(
|
|
837
|
+
assert_eq!(
|
|
838
|
+
reshape_states(&rows),
|
|
839
|
+
json!({"Client": {"active": {"tone": "good"}}})
|
|
840
|
+
);
|
|
697
841
|
}
|
|
698
842
|
}
|