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
data/rust/host/src/boot.rs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
//! Lambda custom-runtime entry point (or a long-lived server under
|
|
2
2
|
//! HECKS_SERVE_MODE=1): boots Postgres/wasm state, then dispatches events.
|
|
3
3
|
|
|
4
|
-
use crate::
|
|
4
|
+
use crate::host::Host;
|
|
5
|
+
use crate::{
|
|
6
|
+
auth, extension, ir, journal, lambda_client, log, mint, rate_limit, secrets, server,
|
|
7
|
+
storage_shape,
|
|
8
|
+
};
|
|
5
9
|
|
|
6
10
|
use lambda_runtime::{service_fn, Error, LambdaEvent};
|
|
7
11
|
use std::path::PathBuf;
|
|
@@ -37,8 +41,10 @@ pub async fn run() -> Result<(), Error> {
|
|
|
37
41
|
let secret_fetcher = secrets::AwsSecretFetcher::from_env().await;
|
|
38
42
|
let database_url = match std::env::var("DB_SECRET_ARN") {
|
|
39
43
|
Ok(secret_arn) => {
|
|
40
|
-
let db_host = std::env::var("DB_HOST")
|
|
41
|
-
|
|
44
|
+
let db_host = std::env::var("DB_HOST")
|
|
45
|
+
.map_err(|_| "DB_HOST is required when DB_SECRET_ARN is set")?;
|
|
46
|
+
let db_name = std::env::var("DB_NAME")
|
|
47
|
+
.map_err(|_| "DB_NAME is required when DB_SECRET_ARN is set")?;
|
|
42
48
|
let secret_json = secret_fetcher
|
|
43
49
|
.fetch_secret_string(&secret_arn)
|
|
44
50
|
.await
|
|
@@ -56,10 +62,7 @@ pub async fn run() -> Result<(), Error> {
|
|
|
56
62
|
// Secrets-Manager-at-cold-start approach as DB_SECRET_ARN above,
|
|
57
63
|
// applied to auth.rs's GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET and
|
|
58
64
|
// web.rs's SESSION_SECRET.
|
|
59
|
-
//
|
|
60
|
-
// Safety: this runs before any request is dispatched, so nothing
|
|
61
|
-
// else reads or writes the environment concurrently with these
|
|
62
|
-
// `set_var` calls.
|
|
65
|
+
// They go into `secrets::provide`, not the process environment.
|
|
63
66
|
if let Ok(oauth_secret_id) = std::env::var("GOOGLE_OAUTH_SECRET_ID") {
|
|
64
67
|
let secret_json = secret_fetcher
|
|
65
68
|
.fetch_secret_string(&oauth_secret_id)
|
|
@@ -67,10 +70,8 @@ pub async fn run() -> Result<(), Error> {
|
|
|
67
70
|
.map_err(|e| format!("fetching GOOGLE_OAUTH_SECRET_ID from Secrets Manager: {e:#}"))?;
|
|
68
71
|
let client_id = secrets::extract_field(&secret_json, "client_id")?;
|
|
69
72
|
let client_secret = secrets::extract_field(&secret_json, "client_secret")?;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
std::env::set_var("GOOGLE_CLIENT_SECRET", client_secret);
|
|
73
|
-
}
|
|
73
|
+
secrets::provide("GOOGLE_CLIENT_ID", client_id);
|
|
74
|
+
secrets::provide("GOOGLE_CLIENT_SECRET", client_secret);
|
|
74
75
|
}
|
|
75
76
|
if let Ok(session_secret_arn) = std::env::var("SESSION_SECRET_ARN") {
|
|
76
77
|
let secret_json = secret_fetcher
|
|
@@ -78,9 +79,7 @@ pub async fn run() -> Result<(), Error> {
|
|
|
78
79
|
.await
|
|
79
80
|
.map_err(|e| format!("fetching SESSION_SECRET_ARN from Secrets Manager: {e:#}"))?;
|
|
80
81
|
let session_secret = secrets::extract_field(&secret_json, "session_secret")?;
|
|
81
|
-
|
|
82
|
-
std::env::set_var("SESSION_SECRET", session_secret);
|
|
83
|
-
}
|
|
82
|
+
secrets::provide("SESSION_SECRET", session_secret);
|
|
84
83
|
}
|
|
85
84
|
for extension in extension::installed() {
|
|
86
85
|
extension.boot_secrets(&secret_fetcher).await;
|
|
@@ -125,7 +124,8 @@ pub async fn run() -> Result<(), Error> {
|
|
|
125
124
|
}
|
|
126
125
|
// NoTlsStream and RustlsStream can't unify in one branch, so the
|
|
127
126
|
// two paths are spawned separately.
|
|
128
|
-
let connect_phase =
|
|
127
|
+
let connect_phase =
|
|
128
|
+
log::phase_with("db_connect", serde_json::json!({ "tls": !local_postgres }));
|
|
129
129
|
let client = if local_postgres {
|
|
130
130
|
let (client, connection) = config
|
|
131
131
|
.connect(tokio_postgres::NoTls)
|
|
@@ -164,11 +164,17 @@ pub async fn run() -> Result<(), Error> {
|
|
|
164
164
|
// existing schema is the ordinary case on every boot after the first.
|
|
165
165
|
if let Some(schema) = &schema {
|
|
166
166
|
client
|
|
167
|
-
.batch_execute(&format!(
|
|
167
|
+
.batch_execute(&format!(
|
|
168
|
+
"CREATE SCHEMA IF NOT EXISTS {}",
|
|
169
|
+
journal::quote_ident(schema)
|
|
170
|
+
))
|
|
168
171
|
.await
|
|
169
172
|
.map_err(|e| format!("creating schema {schema:?}: {e:#}"))?;
|
|
170
173
|
client
|
|
171
|
-
.batch_execute(&format!(
|
|
174
|
+
.batch_execute(&format!(
|
|
175
|
+
"SET search_path TO {}",
|
|
176
|
+
journal::quote_ident(schema)
|
|
177
|
+
))
|
|
172
178
|
.await
|
|
173
179
|
.map_err(|e| format!("setting search_path to {schema:?}: {e:#}"))?;
|
|
174
180
|
}
|
|
@@ -187,7 +193,11 @@ pub async fn run() -> Result<(), Error> {
|
|
|
187
193
|
let mut aggregates: Vec<mint::Aggregate> = ir::lineage_capable_aggregates(ir)
|
|
188
194
|
.into_iter()
|
|
189
195
|
.map(|(qualified, storage_name)| {
|
|
190
|
-
let name = qualified
|
|
196
|
+
let name = qualified
|
|
197
|
+
.rsplit("::")
|
|
198
|
+
.next()
|
|
199
|
+
.unwrap_or(&qualified)
|
|
200
|
+
.to_string();
|
|
191
201
|
mint::Aggregate { name, storage_name }
|
|
192
202
|
})
|
|
193
203
|
.collect();
|
|
@@ -195,7 +205,12 @@ pub async fn run() -> Result<(), Error> {
|
|
|
195
205
|
// aggregate can be vendored, so it may be missing from this domain's
|
|
196
206
|
// lineage-capable list. Mint its head anyway for Google-auth to read.
|
|
197
207
|
if let Some(membership) = ir::membership_provider(ir) {
|
|
198
|
-
let name = membership
|
|
208
|
+
let name = membership
|
|
209
|
+
.aggregate
|
|
210
|
+
.rsplit("::")
|
|
211
|
+
.next()
|
|
212
|
+
.unwrap_or(&membership.aggregate)
|
|
213
|
+
.to_string();
|
|
199
214
|
let storage_name = journal::snake(&name);
|
|
200
215
|
if !aggregates.iter().any(|a| a.storage_name == storage_name) {
|
|
201
216
|
aggregates.push(mint::Aggregate { name, storage_name });
|
|
@@ -205,14 +220,16 @@ pub async fn run() -> Result<(), Error> {
|
|
|
205
220
|
// The era-partitioned lineage subsystem is provisioned only when
|
|
206
221
|
// something needs it: a lineage-capable aggregate, or Google auth
|
|
207
222
|
// configured (ADR 0034 requires it for Member sessions either way).
|
|
208
|
-
let needs_lineage = !aggregates.is_empty() ||
|
|
223
|
+
let needs_lineage = !aggregates.is_empty() || secrets::var("GOOGLE_CLIENT_ID").is_ok();
|
|
209
224
|
|
|
210
225
|
let era: Option<i32> = if needs_lineage {
|
|
211
226
|
// Idempotent (gated by `provisioner?`); must run before the
|
|
212
227
|
// first `journal::held_eras` call, since `hecks_eras` doesn't
|
|
213
228
|
// exist yet on a truly fresh Postgres instance.
|
|
214
229
|
let phase = log::phase("ensure_base");
|
|
215
|
-
mint::ensure_base(&client, &domain)
|
|
230
|
+
mint::ensure_base(&client, &domain)
|
|
231
|
+
.await
|
|
232
|
+
.map_err(|e| format!("provisioning hecks_eras for {domain}: {e:#}"))?;
|
|
216
233
|
phase.end();
|
|
217
234
|
|
|
218
235
|
// Compares this binary's own shape hash (`storage_shape`, over
|
|
@@ -222,20 +239,26 @@ pub async fn run() -> Result<(), Error> {
|
|
|
222
239
|
// `hecks scaffold_translation` (no edge covers it) or
|
|
223
240
|
// `hecks audit_translation --approve` (needs review) otherwise.
|
|
224
241
|
let era_phase = log::phase("era_resolve");
|
|
225
|
-
let held = journal::held_eras(&client, &domain)
|
|
242
|
+
let held = journal::held_eras(&client, &domain)
|
|
243
|
+
.await
|
|
244
|
+
.map_err(|e| format!("checking hecks_eras for {domain}: {e:#}"))?;
|
|
226
245
|
|
|
227
246
|
// The decision itself is a pure, unit-tested function
|
|
228
247
|
// (`mint::decide_boot_action`); everything below just carries
|
|
229
248
|
// out whichever of its four outcomes came back.
|
|
230
249
|
let decision = mint::decide_boot_action(&held, &my_label);
|
|
231
|
-
era_phase
|
|
250
|
+
era_phase
|
|
251
|
+
.end_with(serde_json::json!({ "held_eras": held.len(), "decision": decision.name() }));
|
|
232
252
|
let ordinal: i32 = match decision {
|
|
233
253
|
// Adopting an existing era still provisions what this binary
|
|
234
254
|
// is about to write into, the same as minting does --
|
|
235
255
|
// skipping it would let a matched-era host write into
|
|
236
256
|
// head-snapshot tables that were never created.
|
|
237
257
|
mint::BootDecision::UseExisting { ordinal } => {
|
|
238
|
-
let phase = log::phase_with(
|
|
258
|
+
let phase = log::phase_with(
|
|
259
|
+
"adopt_head_snapshots",
|
|
260
|
+
serde_json::json!({ "era": ordinal }),
|
|
261
|
+
);
|
|
239
262
|
mint::adopt_head_snapshots(&client, &domain, &aggregates, ordinal)
|
|
240
263
|
.await
|
|
241
264
|
.map_err(|e| format!("adopting era {ordinal} of {domain}: {e:#}"))?;
|
|
@@ -243,10 +266,14 @@ pub async fn run() -> Result<(), Error> {
|
|
|
243
266
|
ordinal
|
|
244
267
|
}
|
|
245
268
|
mint::BootDecision::HoldFirst => {
|
|
246
|
-
let source_text =
|
|
247
|
-
|
|
269
|
+
let source_text = ir
|
|
270
|
+
.get("source_text")
|
|
271
|
+
.and_then(serde_json::Value::as_str)
|
|
272
|
+
.ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
|
|
248
273
|
let phase = log::phase_with("hold_first", serde_json::json!({ "era": 1 }));
|
|
249
|
-
mint::hold_first(&client, &domain, source_text, ir, &aggregates, None)
|
|
274
|
+
mint::hold_first(&client, &domain, source_text, ir, &aggregates, None)
|
|
275
|
+
.await
|
|
276
|
+
.map_err(|e| format!("minting era 1 of {domain}: {e:#}"))?;
|
|
250
277
|
phase.end();
|
|
251
278
|
1
|
|
252
279
|
}
|
|
@@ -258,8 +285,15 @@ pub async fn run() -> Result<(), Error> {
|
|
|
258
285
|
)
|
|
259
286
|
.into());
|
|
260
287
|
}
|
|
261
|
-
mint::BootDecision::Mint {
|
|
262
|
-
|
|
288
|
+
mint::BootDecision::Mint {
|
|
289
|
+
ordinal,
|
|
290
|
+
from_ordinal,
|
|
291
|
+
from_label,
|
|
292
|
+
} => {
|
|
293
|
+
let mut labels: Vec<String> = held
|
|
294
|
+
.iter()
|
|
295
|
+
.map(|held_era| held_era.label.clone().unwrap_or_default())
|
|
296
|
+
.collect();
|
|
263
297
|
labels.push(my_label.clone());
|
|
264
298
|
let edges = mint::parse_edges(ir, &domain);
|
|
265
299
|
let chain = mint::edge_chain(&edges, &labels).map_err(|e| {
|
|
@@ -270,40 +304,60 @@ pub async fn run() -> Result<(), Error> {
|
|
|
270
304
|
)
|
|
271
305
|
})?;
|
|
272
306
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
let approval_phase = log::phase_with("approval_check", serde_json::json!({ "edges": chain.len(), "committed": committed_approvals.len() }));
|
|
280
|
-
for edge in &chain {
|
|
281
|
-
let Some(raw_edge) = raw_edges.iter().find(|candidate| {
|
|
282
|
-
candidate.get("domain").and_then(serde_json::Value::as_str) == Some(domain.as_str())
|
|
283
|
-
&& candidate.get("from").and_then(serde_json::Value::as_str) == Some(edge.from.as_str())
|
|
284
|
-
&& candidate.get("to").and_then(serde_json::Value::as_str) == Some(edge.to.as_str())
|
|
285
|
-
}) else {
|
|
286
|
-
return Err(format!("cannot boot: {domain}'s edge {} -> {} vanished between parsing and approval-checking it", edge.from, edge.to).into());
|
|
287
|
-
};
|
|
288
|
-
approval::check(&client, &domain, raw_edge, ordinal, &committed_approvals).await.map_err(|e| format!("{e:#}"))?;
|
|
289
|
-
}
|
|
290
|
-
approval_phase.end();
|
|
307
|
+
// The raw ir.json edges, matched to the chain by from/to pair for the audit.
|
|
308
|
+
let raw_edges = ir
|
|
309
|
+
.get("translations")
|
|
310
|
+
.and_then(serde_json::Value::as_array)
|
|
311
|
+
.cloned()
|
|
312
|
+
.unwrap_or_default();
|
|
291
313
|
|
|
292
314
|
// Runs before anything is minted, over the live compiled
|
|
293
315
|
// chain (plain SELECTs, never a persisted matview), so a
|
|
294
316
|
// refusal leaves no half-born era.
|
|
295
|
-
let watermarks: std::collections::HashMap<i32, Option<i64>> = held
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
317
|
+
let watermarks: std::collections::HashMap<i32, Option<i64>> = held
|
|
318
|
+
.iter()
|
|
319
|
+
.map(|held_era| (held_era.ordinal, held_era.watermark))
|
|
320
|
+
.collect();
|
|
321
|
+
let audit_phase = log::phase_with(
|
|
322
|
+
"audit_before_mint",
|
|
323
|
+
serde_json::json!({ "era": ordinal, "edges": chain.len() }),
|
|
324
|
+
);
|
|
325
|
+
mint::audit_before_mint(
|
|
326
|
+
&client,
|
|
327
|
+
&domain,
|
|
328
|
+
ir,
|
|
329
|
+
&aggregates,
|
|
330
|
+
ordinal,
|
|
331
|
+
&chain,
|
|
332
|
+
&raw_edges,
|
|
333
|
+
&watermarks,
|
|
334
|
+
)
|
|
335
|
+
.await
|
|
336
|
+
.map_err(|e| format!("{e:#}"))?;
|
|
300
337
|
audit_phase.end();
|
|
301
338
|
|
|
302
|
-
let held_text = ir
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
.
|
|
306
|
-
|
|
339
|
+
let held_text = ir
|
|
340
|
+
.get("source_text")
|
|
341
|
+
.and_then(serde_json::Value::as_str)
|
|
342
|
+
.ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
|
|
343
|
+
let mint_phase = log::phase_with(
|
|
344
|
+
"mint_era",
|
|
345
|
+
serde_json::json!({ "era": ordinal, "edges": chain.len() }),
|
|
346
|
+
);
|
|
347
|
+
mint::mint_era(
|
|
348
|
+
&client,
|
|
349
|
+
&domain,
|
|
350
|
+
ordinal,
|
|
351
|
+
&my_hash,
|
|
352
|
+
&my_label,
|
|
353
|
+
held_text,
|
|
354
|
+
&aggregates,
|
|
355
|
+
&chain,
|
|
356
|
+
None,
|
|
357
|
+
&mint::lifecycle_defaults(ir),
|
|
358
|
+
)
|
|
359
|
+
.await
|
|
360
|
+
.map_err(|e| format!("minting era {ordinal} of {domain}: {e:#}"))?;
|
|
307
361
|
mint_phase.end();
|
|
308
362
|
ordinal
|
|
309
363
|
}
|
|
@@ -317,7 +371,11 @@ pub async fn run() -> Result<(), Error> {
|
|
|
317
371
|
// -- never "everything this kernel can mutate". A domain that binds
|
|
318
372
|
// everything to plain `Postgres` mirrors nothing.
|
|
319
373
|
let mirrored = ir::mirrored_aggregates(ir);
|
|
320
|
-
let lineage_config = Arc::new(journal::LineageConfig {
|
|
374
|
+
let lineage_config = Arc::new(journal::LineageConfig {
|
|
375
|
+
domain,
|
|
376
|
+
era,
|
|
377
|
+
mirrored: Some(mirrored),
|
|
378
|
+
});
|
|
321
379
|
|
|
322
380
|
// Mutex, not a bare Arc<Client> -- dispatch::handle needs
|
|
323
381
|
// Client::transaction (&mut Client) to hold the advisory lock
|
|
@@ -338,9 +396,19 @@ pub async fn run() -> Result<(), Error> {
|
|
|
338
396
|
"boot",
|
|
339
397
|
serde_json::json!({ "mode": "serve", "domain": &lineage_config.domain, "era": &my_label, "boot_ms": boot_started.elapsed().as_millis() as u64 }),
|
|
340
398
|
);
|
|
341
|
-
let version = server::version_body(
|
|
399
|
+
let version = server::version_body(
|
|
400
|
+
&my_label,
|
|
401
|
+
&my_hash,
|
|
402
|
+
std::env::var("HECKS_BUILD").ok().as_deref(),
|
|
403
|
+
);
|
|
342
404
|
let limits = Arc::new(rate_limit::RateLimits::from_env());
|
|
343
|
-
let state = server::ServerState {
|
|
405
|
+
let state = server::ServerState {
|
|
406
|
+
client,
|
|
407
|
+
wasm_path,
|
|
408
|
+
lineage_config,
|
|
409
|
+
invoker,
|
|
410
|
+
limits,
|
|
411
|
+
};
|
|
344
412
|
return server::serve(state, version).await;
|
|
345
413
|
}
|
|
346
414
|
|
|
@@ -351,7 +419,11 @@ pub async fn run() -> Result<(), Error> {
|
|
|
351
419
|
let invoker = Arc::clone(&invoker);
|
|
352
420
|
async move {
|
|
353
421
|
let (body, _context) = event.into_parts();
|
|
354
|
-
server::dispatch_body(
|
|
422
|
+
server::dispatch_body(
|
|
423
|
+
body,
|
|
424
|
+
Host::new(&client, &wasm_path, &lineage_config, invoker.as_ref()),
|
|
425
|
+
)
|
|
426
|
+
.await
|
|
355
427
|
}
|
|
356
428
|
}))
|
|
357
429
|
.await
|
|
@@ -364,12 +436,17 @@ fn parse_database_url(url: &str) -> Result<tokio_postgres::Config, String> {
|
|
|
364
436
|
let rest = url
|
|
365
437
|
.strip_prefix("postgres://")
|
|
366
438
|
.or_else(|| url.strip_prefix("postgresql://"))
|
|
367
|
-
.ok_or_else(||
|
|
439
|
+
.ok_or_else(|| {
|
|
440
|
+
"DATABASE_URL doesn't start with postgres:// or postgresql://".to_string()
|
|
441
|
+
})?;
|
|
368
442
|
|
|
369
443
|
let (authority, db_and_query) = rest
|
|
370
444
|
.split_once('/')
|
|
371
445
|
.ok_or_else(|| "DATABASE_URL has no '/' separating host from database name".to_string())?;
|
|
372
|
-
let dbname = db_and_query
|
|
446
|
+
let dbname = db_and_query
|
|
447
|
+
.split(['?', '#'])
|
|
448
|
+
.next()
|
|
449
|
+
.unwrap_or(db_and_query);
|
|
373
450
|
|
|
374
451
|
let (credentials, host_and_port) = match authority.rsplit_once('@') {
|
|
375
452
|
Some((credentials, host_and_port)) => (Some(credentials), host_and_port),
|
|
@@ -404,7 +481,7 @@ fn database_url_is_local(config: &tokio_postgres::Config) -> bool {
|
|
|
404
481
|
use tokio_postgres::config::Host;
|
|
405
482
|
config.get_hosts().iter().any(|host| match host {
|
|
406
483
|
Host::Tcp(name) => name == "localhost" || name == "127.0.0.1" || name == "::1",
|
|
407
|
-
_ => false,
|
|
484
|
+
Host::Unix(_) => false,
|
|
408
485
|
})
|
|
409
486
|
}
|
|
410
487
|
|
|
@@ -414,10 +491,9 @@ mod tests {
|
|
|
414
491
|
|
|
415
492
|
#[test]
|
|
416
493
|
fn parses_a_password_with_uri_reserved_characters() {
|
|
417
|
-
let config =
|
|
418
|
-
"postgres://postgres:T3st$Pa?#ss*>Word@db.example.com:5432/pizzas"
|
|
419
|
-
|
|
420
|
-
.expect("should parse despite ?/# in the password");
|
|
494
|
+
let config =
|
|
495
|
+
parse_database_url("postgres://postgres:T3st$Pa?#ss*>Word@db.example.com:5432/pizzas")
|
|
496
|
+
.expect("should parse despite ?/# in the password");
|
|
421
497
|
assert_eq!(config.get_hosts().len(), 1);
|
|
422
498
|
assert_eq!(config.get_ports(), &[5432]);
|
|
423
499
|
assert_eq!(config.get_user(), Some("postgres"));
|
|
@@ -7,11 +7,20 @@ use super::*;
|
|
|
7
7
|
use crate::fuzz_support::*;
|
|
8
8
|
|
|
9
9
|
fn envelope_is_structured(response: &Value) {
|
|
10
|
-
let status = response["statusCode"]
|
|
11
|
-
|
|
10
|
+
let status = response["statusCode"]
|
|
11
|
+
.as_u64()
|
|
12
|
+
.expect("a refusal carries its status");
|
|
13
|
+
assert!(
|
|
14
|
+
(400..600).contains(&status),
|
|
15
|
+
"a refusal is a 4xx/5xx, got {status}"
|
|
16
|
+
);
|
|
12
17
|
assert_eq!(response["headers"]["content-type"], "application/json");
|
|
13
|
-
let body: Value = serde_json::from_str(response["body"].as_str().expect("a JSON body"))
|
|
14
|
-
|
|
18
|
+
let body: Value = serde_json::from_str(response["body"].as_str().expect("a JSON body"))
|
|
19
|
+
.expect("the body is JSON");
|
|
20
|
+
assert!(
|
|
21
|
+
body["error"].is_string() && body["message"].is_string(),
|
|
22
|
+
"the refusal names itself: {body}"
|
|
23
|
+
);
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
fn client_aggregate() -> Value {
|
|
@@ -45,12 +54,22 @@ fn parsed_body_refuses_everything_that_is_not_an_object_with_a_structured_400()
|
|
|
45
54
|
mutate_text(&mut rng, &valid)
|
|
46
55
|
}
|
|
47
56
|
2 => random_value(&mut rng, 0).to_string(),
|
|
48
|
-
_ => format!(
|
|
57
|
+
_ => format!(
|
|
58
|
+
"{}{}",
|
|
59
|
+
" \t\n".repeat(rng.below(3)),
|
|
60
|
+
random_value(&mut rng, 1)
|
|
61
|
+
),
|
|
49
62
|
};
|
|
50
63
|
match parsed_body(&text) {
|
|
51
64
|
Ok(body) => {
|
|
52
|
-
assert!(
|
|
53
|
-
|
|
65
|
+
assert!(
|
|
66
|
+
body.is_object(),
|
|
67
|
+
"seed {seed}: {text:?} parsed to a non-object"
|
|
68
|
+
);
|
|
69
|
+
assert!(
|
|
70
|
+
text.trim().is_empty() || serde_json::from_str::<Value>(&text).is_ok(),
|
|
71
|
+
"seed {seed}: {text:?} is not JSON yet was accepted"
|
|
72
|
+
);
|
|
54
73
|
}
|
|
55
74
|
Err(refusal) => {
|
|
56
75
|
assert_eq!(refusal["statusCode"], 400, "seed {seed}: {text:?}");
|
|
@@ -74,7 +93,19 @@ fn parsed_body_survives_depth_width_and_size() {
|
|
|
74
93
|
] {
|
|
75
94
|
let _ = parsed_body(&text);
|
|
76
95
|
}
|
|
77
|
-
for text in [
|
|
96
|
+
for text in [
|
|
97
|
+
"[]",
|
|
98
|
+
"1",
|
|
99
|
+
"\"s\"",
|
|
100
|
+
"null",
|
|
101
|
+
"true",
|
|
102
|
+
"{",
|
|
103
|
+
"{\"a\":1}{}",
|
|
104
|
+
"{} {}",
|
|
105
|
+
"\u{feff}{}",
|
|
106
|
+
"{\"a\":NaN}",
|
|
107
|
+
"{\"a\":1e999}",
|
|
108
|
+
] {
|
|
78
109
|
if let Err(refusal) = parsed_body(text) {
|
|
79
110
|
assert_eq!(refusal["statusCode"], 400, "{text:?}");
|
|
80
111
|
}
|
|
@@ -93,8 +124,15 @@ fn slugify_always_answers_a_clean_non_empty_slug() {
|
|
|
93
124
|
let text = random_text(&mut rng);
|
|
94
125
|
let slug = slugify(&text);
|
|
95
126
|
assert!(!slug.is_empty(), "seed {seed}: {text:?} slugged to nothing");
|
|
96
|
-
assert!(
|
|
97
|
-
|
|
127
|
+
assert!(
|
|
128
|
+
slug.chars()
|
|
129
|
+
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
|
|
130
|
+
"seed {seed}: {text:?} -> {slug:?}"
|
|
131
|
+
);
|
|
132
|
+
assert!(
|
|
133
|
+
!slug.starts_with('-') && !slug.ends_with('-') && !slug.contains("--"),
|
|
134
|
+
"seed {seed}: {text:?} -> {slug:?}"
|
|
135
|
+
);
|
|
98
136
|
}
|
|
99
137
|
});
|
|
100
138
|
}
|
|
@@ -113,26 +151,55 @@ fn identity_minting_survives_any_rule_and_leaves_args_alone_when_it_refuses() {
|
|
|
113
151
|
continue;
|
|
114
152
|
}
|
|
115
153
|
let value = match key {
|
|
116
|
-
"field" if rng.chance(2) => json!(*rng.pick(&[
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
154
|
+
"field" if rng.chance(2) => json!(*rng.pick(&[
|
|
155
|
+
"reference",
|
|
156
|
+
"name",
|
|
157
|
+
"owner",
|
|
158
|
+
"status",
|
|
159
|
+
"missing",
|
|
160
|
+
""
|
|
161
|
+
])),
|
|
162
|
+
"strategy" if rng.chance(2) => {
|
|
163
|
+
json!(*rng.pick(&["slug", "sequence", "port", "nope", ""]))
|
|
164
|
+
}
|
|
165
|
+
"source" if rng.chance(2) => {
|
|
166
|
+
json!(*rng.pick(&["name", "reference", "owner", "missing", ""]))
|
|
167
|
+
}
|
|
168
|
+
"pad" if rng.chance(2) => json!(*rng.pick(&[
|
|
169
|
+
0u64,
|
|
170
|
+
1,
|
|
171
|
+
3,
|
|
172
|
+
64,
|
|
173
|
+
65,
|
|
174
|
+
1_000,
|
|
175
|
+
1_000_000,
|
|
176
|
+
1u64 << 40,
|
|
177
|
+
1u64 << 62,
|
|
178
|
+
u64::MAX
|
|
179
|
+
])),
|
|
120
180
|
_ => random_value(&mut rng, 2),
|
|
121
181
|
};
|
|
122
182
|
rule.insert(key.to_string(), value);
|
|
123
183
|
}
|
|
124
|
-
let presentation =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
let presentation =
|
|
185
|
+
json!({"collections": {"Client": {"identity": Value::Object(rule)}}});
|
|
186
|
+
// Drawn before the coin flip, so a seed replays the same stream.
|
|
187
|
+
let drawn = random_value(&mut rng, 0);
|
|
188
|
+
let mut args = if rng.chance(2) {
|
|
189
|
+
json!({"name": random_value(&mut rng, 2)})
|
|
190
|
+
} else {
|
|
191
|
+
drawn
|
|
192
|
+
};
|
|
129
193
|
let before = args.clone();
|
|
130
194
|
let existing = *rng.pick(&[0usize, 1, 41, 999_999, usize::MAX - 1]);
|
|
131
195
|
match apply_identity(&presentation, &aggregate, &mut args, existing) {
|
|
132
196
|
Ok(()) => {}
|
|
133
197
|
Err(refusal) => {
|
|
134
198
|
envelope_is_structured(&refusal);
|
|
135
|
-
assert_eq!(
|
|
199
|
+
assert_eq!(
|
|
200
|
+
args, before,
|
|
201
|
+
"seed {seed}: a refused identity must not have touched the args"
|
|
202
|
+
);
|
|
136
203
|
}
|
|
137
204
|
}
|
|
138
205
|
}
|
|
@@ -146,13 +213,19 @@ fn an_absurd_sequence_pad_is_refused_rather_than_allocating_it() {
|
|
|
146
213
|
for pad in [65u64, 1_000_000, 1 << 40, u64::MAX] {
|
|
147
214
|
let presentation = json!({"collections": {"Client": {"identity": {"field": "reference", "strategy": "sequence", "prefix": "C-", "pad": pad}}}});
|
|
148
215
|
let mut args = json!({"name": {"value": "Acme"}});
|
|
149
|
-
let refusal = apply_identity(&presentation, &aggregate, &mut args, 1)
|
|
216
|
+
let refusal = apply_identity(&presentation, &aggregate, &mut args, 1)
|
|
217
|
+
.expect_err("a pad this wide is refused");
|
|
150
218
|
envelope_is_structured(&refusal);
|
|
151
|
-
assert_eq!(
|
|
219
|
+
assert_eq!(
|
|
220
|
+
args,
|
|
221
|
+
json!({"name": {"value": "Acme"}}),
|
|
222
|
+
"pad {pad}: nothing was minted"
|
|
223
|
+
);
|
|
152
224
|
}
|
|
153
225
|
let presentation = json!({"collections": {"Client": {"identity": {"field": "reference", "strategy": "sequence", "prefix": "C-", "pad": 64}}}});
|
|
154
226
|
let mut args = json!({});
|
|
155
|
-
apply_identity(&presentation, &aggregate, &mut args, 0)
|
|
227
|
+
apply_identity(&presentation, &aggregate, &mut args, 0)
|
|
228
|
+
.expect("the widest allowed pad still mints");
|
|
156
229
|
assert_eq!(args["reference"]["value"].as_str().unwrap().len(), 2 + 64);
|
|
157
230
|
}
|
|
158
231
|
|
|
@@ -169,9 +242,19 @@ fn preconditions_survive_any_config_and_instances_shape() {
|
|
|
169
242
|
} else {
|
|
170
243
|
json!({"field": *rng.pick(&["owner", "name", "status", "missing"]), "state": random_value(&mut rng, 2), "message": random_value(&mut rng, 2)})
|
|
171
244
|
};
|
|
172
|
-
let args = if rng.chance(3) {
|
|
173
|
-
|
|
174
|
-
|
|
245
|
+
let args = if rng.chance(3) {
|
|
246
|
+
random_value(&mut rng, 0)
|
|
247
|
+
} else {
|
|
248
|
+
json!({"owner": random_value(&mut rng, 2)})
|
|
249
|
+
};
|
|
250
|
+
let instances = if rng.chance(3) {
|
|
251
|
+
random_value(&mut rng, 0)
|
|
252
|
+
} else {
|
|
253
|
+
json!({format!("{}::Member#m1", domain_name(&domain)): random_value(&mut rng, 2)})
|
|
254
|
+
};
|
|
255
|
+
if let Err(refusal) =
|
|
256
|
+
check_precondition(&domain, &aggregate, &rule, &args, &instances)
|
|
257
|
+
{
|
|
175
258
|
envelope_is_structured(&refusal);
|
|
176
259
|
}
|
|
177
260
|
}
|
|
@@ -203,9 +286,12 @@ fn sorting_records_of_mixed_types_never_panics_on_an_inconsistent_order() {
|
|
|
203
286
|
})
|
|
204
287
|
.collect();
|
|
205
288
|
for descending in [false, true] {
|
|
206
|
-
let spec = Some(SortSpec {
|
|
207
|
-
|
|
208
|
-
|
|
289
|
+
let spec = Some(SortSpec {
|
|
290
|
+
field: "col".to_string(),
|
|
291
|
+
descending,
|
|
292
|
+
});
|
|
293
|
+
sort_records(&mut records, spec.as_ref());
|
|
294
|
+
sort_records(&mut records, None);
|
|
209
295
|
}
|
|
210
296
|
}
|
|
211
297
|
});
|
|
@@ -218,7 +304,7 @@ fn query_arguments_and_sort_params_survive_hostile_input() {
|
|
|
218
304
|
guarded(&format!("seed {seed}"), move || {
|
|
219
305
|
let mut rng = Rng::new(seed ^ 0x9A);
|
|
220
306
|
let aggregate = client_aggregate();
|
|
221
|
-
let domain = json!({"name": "Crm", "aggregates": [aggregate
|
|
307
|
+
let domain = json!({"name": "Crm", "aggregates": [aggregate]});
|
|
222
308
|
for _ in 0..30 {
|
|
223
309
|
let named = if rng.chance(3) {
|
|
224
310
|
random_value(&mut rng, 0)
|
|
@@ -235,10 +321,22 @@ fn query_arguments_and_sort_params_survive_hostile_input() {
|
|
|
235
321
|
params.insert("name".to_string(), random_text(&mut rng));
|
|
236
322
|
let _ = query_args_from_params(&aggregate, &named, ¶ms);
|
|
237
323
|
|
|
238
|
-
let sort: HashMap<String, String> = [
|
|
324
|
+
let sort: HashMap<String, String> = [
|
|
325
|
+
("sort", random_text(&mut rng)),
|
|
326
|
+
("direction", random_text(&mut rng)),
|
|
327
|
+
]
|
|
328
|
+
.into_iter()
|
|
329
|
+
.map(|(k, v)| (k.to_string(), v))
|
|
330
|
+
.collect();
|
|
239
331
|
let config = random_value(&mut rng, 0);
|
|
240
332
|
let _ = resolve_sort(&domain, &config, "Client", &aggregate, &sort);
|
|
241
|
-
let _ = resolve_sort(
|
|
333
|
+
let _ = resolve_sort(
|
|
334
|
+
&domain,
|
|
335
|
+
&json!({}),
|
|
336
|
+
"Client",
|
|
337
|
+
&aggregate,
|
|
338
|
+
&HashMap::from([("sort".to_string(), "__state__".to_string())]),
|
|
339
|
+
);
|
|
242
340
|
}
|
|
243
341
|
});
|
|
244
342
|
}
|