hecks 3.13.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.bluebook +4 -4
- 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/attribute_collector.rb +15 -5
- 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/named_pattern.rb +33 -0
- 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 +3 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- 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/deploy/bluebook/deploy.bluebook +34 -34
- 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/framework/bluebook/change.bluebook +2 -2
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- 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 +58 -37
- 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 +15 -92
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +10 -16
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- 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/quality_control/quality_control.bluebook +51 -51
- 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/site/bluebook/site.bluebook +5 -5
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- 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/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
- 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 +191 -0
- data/rust/host/src/payments/tests.rs +1445 -275
- data/rust/host/src/payments.rs +409 -121
- 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 -0
- data/rust/parser/src/build/named_pattern.rs +26 -0
- 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 -510
- 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 +43 -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 +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
- data/rust/host/src/approval.rs +0 -652
|
@@ -17,9 +17,9 @@ pub trait LambdaInvoker: Send + Sync {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Mirrors Ruby's `"hecks-#{domain.downcase}"` — deployed stack names depend on
|
|
20
|
-
// this exact casing.
|
|
20
|
+
// this exact casing, down to a word-final capital sigma, so it lowercases as Ruby does.
|
|
21
21
|
pub fn function_name_for(domain: &str) -> String {
|
|
22
|
-
format!("hecks-{}", domain
|
|
22
|
+
format!("hecks-{}", crate::hecks_naming::ruby_downcase(domain))
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Mirrors `PolicyInterpreter#deliver`'s record shape, minus `on:` — this crate
|
|
@@ -47,11 +47,29 @@ impl CrossDomainDeliveryRecord {
|
|
|
47
47
|
|
|
48
48
|
// A target-side refusal returns `delivered: false`; a genuine invoke fault
|
|
49
49
|
// (`function_error`, or a hard `Err` from `invoke`) returns `Err` instead.
|
|
50
|
-
pub async fn deliver<L: LambdaInvoker + ?Sized>(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let
|
|
50
|
+
pub async fn deliver<L: LambdaInvoker + ?Sized>(
|
|
51
|
+
invoker: &L,
|
|
52
|
+
reaction: &Value,
|
|
53
|
+
) -> anyhow::Result<CrossDomainDeliveryRecord> {
|
|
54
|
+
let policy = reaction
|
|
55
|
+
.get("policy")
|
|
56
|
+
.and_then(Value::as_str)
|
|
57
|
+
.unwrap_or_default()
|
|
58
|
+
.to_string();
|
|
59
|
+
let target_domain = reaction
|
|
60
|
+
.get("target_domain")
|
|
61
|
+
.and_then(Value::as_str)
|
|
62
|
+
.unwrap_or_default()
|
|
63
|
+
.to_string();
|
|
64
|
+
let target_verb = reaction
|
|
65
|
+
.get("target_verb")
|
|
66
|
+
.and_then(Value::as_str)
|
|
67
|
+
.unwrap_or_default()
|
|
68
|
+
.to_string();
|
|
69
|
+
let payload = reaction
|
|
70
|
+
.get("payload")
|
|
71
|
+
.cloned()
|
|
72
|
+
.unwrap_or_else(|| serde_json::json!({}));
|
|
55
73
|
|
|
56
74
|
let function_name = function_name_for(&target_domain);
|
|
57
75
|
// No `"role"` — a policy reaction is system-triggered, the same reasoning
|
|
@@ -61,18 +79,32 @@ pub async fn deliver<L: LambdaInvoker + ?Sized>(invoker: &L, reaction: &Value) -
|
|
|
61
79
|
let outcome = invoker.invoke(&function_name, &request_payload).await?;
|
|
62
80
|
|
|
63
81
|
if outcome.function_error {
|
|
64
|
-
let message = outcome
|
|
65
|
-
|
|
82
|
+
let message = outcome
|
|
83
|
+
.body
|
|
84
|
+
.get("errorMessage")
|
|
85
|
+
.and_then(Value::as_str)
|
|
86
|
+
.map_or_else(|| outcome.body.to_string(), str::to_string);
|
|
87
|
+
anyhow::bail!("Lambda {function_name} ({target_verb}): {message}");
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
let target_side_refusal = outcome
|
|
69
91
|
.body
|
|
70
92
|
.get("refusals")
|
|
71
93
|
.and_then(Value::as_array)
|
|
72
|
-
.and_then(|refusals|
|
|
94
|
+
.and_then(|refusals| {
|
|
95
|
+
refusals
|
|
96
|
+
.iter()
|
|
97
|
+
.find(|r| r.get("verb").and_then(Value::as_str) == Some(target_verb.as_str()))
|
|
98
|
+
});
|
|
73
99
|
|
|
74
100
|
let Some(refusal) = target_side_refusal else {
|
|
75
|
-
return Ok(CrossDomainDeliveryRecord {
|
|
101
|
+
return Ok(CrossDomainDeliveryRecord {
|
|
102
|
+
policy,
|
|
103
|
+
target_domain,
|
|
104
|
+
target_verb,
|
|
105
|
+
delivered: true,
|
|
106
|
+
reason: None,
|
|
107
|
+
});
|
|
76
108
|
};
|
|
77
109
|
|
|
78
110
|
Ok(CrossDomainDeliveryRecord {
|
|
@@ -80,7 +112,10 @@ pub async fn deliver<L: LambdaInvoker + ?Sized>(invoker: &L, reaction: &Value) -
|
|
|
80
112
|
target_domain,
|
|
81
113
|
target_verb,
|
|
82
114
|
delivered: false,
|
|
83
|
-
reason: refusal
|
|
115
|
+
reason: refusal
|
|
116
|
+
.get("error")
|
|
117
|
+
.and_then(Value::as_str)
|
|
118
|
+
.map(String::from),
|
|
84
119
|
})
|
|
85
120
|
}
|
|
86
121
|
|
|
@@ -102,6 +137,8 @@ pub struct DeliveryFailure {
|
|
|
102
137
|
|
|
103
138
|
// Retries only a genuine invoke fault (`Err`); a clean `Ok(delivered: false)`
|
|
104
139
|
// refusal is a business outcome and is never retried.
|
|
140
|
+
// `DeliveryFailure` carries the whole reaction for the dead letter, so its size is intended.
|
|
141
|
+
#[allow(clippy::result_large_err)]
|
|
105
142
|
pub async fn deliver_with_retry<L: LambdaInvoker + ?Sized>(
|
|
106
143
|
invoker: &L,
|
|
107
144
|
reaction: &Value,
|
|
@@ -122,13 +159,28 @@ pub async fn deliver_with_retry<L: LambdaInvoker + ?Sized>(
|
|
|
122
159
|
}
|
|
123
160
|
|
|
124
161
|
Err(DeliveryFailure {
|
|
125
|
-
policy: reaction
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
162
|
+
policy: reaction
|
|
163
|
+
.get("policy")
|
|
164
|
+
.and_then(Value::as_str)
|
|
165
|
+
.unwrap_or_default()
|
|
166
|
+
.to_string(),
|
|
167
|
+
target_domain: reaction
|
|
168
|
+
.get("target_domain")
|
|
169
|
+
.and_then(Value::as_str)
|
|
170
|
+
.unwrap_or_default()
|
|
171
|
+
.to_string(),
|
|
172
|
+
target_verb: reaction
|
|
173
|
+
.get("target_verb")
|
|
174
|
+
.and_then(Value::as_str)
|
|
175
|
+
.unwrap_or_default()
|
|
176
|
+
.to_string(),
|
|
177
|
+
payload: reaction
|
|
178
|
+
.get("payload")
|
|
179
|
+
.cloned()
|
|
180
|
+
.unwrap_or_else(|| serde_json::json!({})),
|
|
181
|
+
// The loop runs at least once and every `Err` arm sets `last_error`, so the fallback is
|
|
182
|
+
// unreachable; it exists only so a zero attempt budget still reports a failure.
|
|
183
|
+
error: last_error.unwrap_or_else(|| anyhow::anyhow!("no delivery attempt ran")),
|
|
132
184
|
attempts: MAX_DELIVERY_ATTEMPTS,
|
|
133
185
|
})
|
|
134
186
|
}
|
|
@@ -144,10 +196,17 @@ impl AwsLambdaInvoker {
|
|
|
144
196
|
// actually wires in `ring` (see Cargo.toml's note on this dependency).
|
|
145
197
|
pub async fn from_env() -> Self {
|
|
146
198
|
let http_client = aws_smithy_http_client::Builder::new()
|
|
147
|
-
.tls_provider(aws_smithy_http_client::tls::Provider::Rustls(
|
|
199
|
+
.tls_provider(aws_smithy_http_client::tls::Provider::Rustls(
|
|
200
|
+
aws_smithy_http_client::tls::rustls_provider::CryptoMode::Ring,
|
|
201
|
+
))
|
|
148
202
|
.build_https();
|
|
149
|
-
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
|
|
150
|
-
|
|
203
|
+
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
|
|
204
|
+
.http_client(http_client)
|
|
205
|
+
.load()
|
|
206
|
+
.await;
|
|
207
|
+
Self {
|
|
208
|
+
client: aws_sdk_lambda::Client::new(&config),
|
|
209
|
+
}
|
|
151
210
|
}
|
|
152
211
|
}
|
|
153
212
|
|
|
@@ -177,12 +236,18 @@ impl LambdaInvoker for AwsLambdaInvoker {
|
|
|
177
236
|
.map_err(|e| anyhow::anyhow!("invoking Lambda {function_name}: {e:?}"))?;
|
|
178
237
|
|
|
179
238
|
let function_error = response.function_error().is_some();
|
|
180
|
-
let bytes = response
|
|
239
|
+
let bytes = response
|
|
240
|
+
.payload()
|
|
241
|
+
.map(|blob| blob.as_ref().to_vec())
|
|
242
|
+
.unwrap_or_default();
|
|
181
243
|
// A non-JSON/empty body reads as `{}` rather than failing the call;
|
|
182
244
|
// `function_error` (checked above) is what actually signals a fault.
|
|
183
245
|
let body: Value = serde_json::from_slice(&bytes).unwrap_or_else(|_| serde_json::json!({}));
|
|
184
246
|
|
|
185
|
-
Ok(InvokeOutcome {
|
|
247
|
+
Ok(InvokeOutcome {
|
|
248
|
+
body,
|
|
249
|
+
function_error,
|
|
250
|
+
})
|
|
186
251
|
}
|
|
187
252
|
}
|
|
188
253
|
|
|
@@ -200,6 +265,30 @@ mod tests {
|
|
|
200
265
|
assert_eq!(function_name_for("Banking"), "hecks-banking");
|
|
201
266
|
}
|
|
202
267
|
|
|
268
|
+
// spec/fixtures/stack_name_vectors.json is asserted by spec/deploy_stack_names_spec.rb against
|
|
269
|
+
// all four Ruby copies of the formula, so a drift in any of them fails a test.
|
|
270
|
+
fn stack_name_vectors() -> Value {
|
|
271
|
+
serde_json::from_str(include_str!(
|
|
272
|
+
"../../../spec/fixtures/stack_name_vectors.json"
|
|
273
|
+
))
|
|
274
|
+
.unwrap()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[test]
|
|
278
|
+
fn function_name_matches_every_shared_stack_name_vector() {
|
|
279
|
+
let vectors = stack_name_vectors();
|
|
280
|
+
let rows = vectors["rows"].as_array().unwrap();
|
|
281
|
+
assert!(!rows.is_empty());
|
|
282
|
+
for row in rows {
|
|
283
|
+
let domain = row["domain"].as_str().unwrap();
|
|
284
|
+
assert_eq!(
|
|
285
|
+
function_name_for(domain),
|
|
286
|
+
row["name"].as_str().unwrap(),
|
|
287
|
+
"{domain}"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
203
292
|
// Records each call and answers with whatever canned outcome the test configured.
|
|
204
293
|
struct MockLambdaInvoker {
|
|
205
294
|
response: Result<InvokeOutcome, String>,
|
|
@@ -208,20 +297,39 @@ mod tests {
|
|
|
208
297
|
|
|
209
298
|
impl MockLambdaInvoker {
|
|
210
299
|
fn answering(body: Value, function_error: bool) -> Self {
|
|
211
|
-
Self {
|
|
300
|
+
Self {
|
|
301
|
+
response: Ok(InvokeOutcome {
|
|
302
|
+
body,
|
|
303
|
+
function_error,
|
|
304
|
+
}),
|
|
305
|
+
calls: Mutex::new(Vec::new()),
|
|
306
|
+
}
|
|
212
307
|
}
|
|
213
308
|
|
|
214
309
|
fn failing(message: &str) -> Self {
|
|
215
|
-
Self {
|
|
310
|
+
Self {
|
|
311
|
+
response: Err(message.to_string()),
|
|
312
|
+
calls: Mutex::new(Vec::new()),
|
|
313
|
+
}
|
|
216
314
|
}
|
|
217
315
|
}
|
|
218
316
|
|
|
219
317
|
#[async_trait::async_trait]
|
|
220
318
|
impl LambdaInvoker for MockLambdaInvoker {
|
|
221
|
-
async fn invoke(
|
|
222
|
-
self
|
|
319
|
+
async fn invoke(
|
|
320
|
+
&self,
|
|
321
|
+
function_name: &str,
|
|
322
|
+
payload: &str,
|
|
323
|
+
) -> anyhow::Result<InvokeOutcome> {
|
|
324
|
+
self.calls
|
|
325
|
+
.lock()
|
|
326
|
+
.unwrap()
|
|
327
|
+
.push((function_name.to_string(), payload.to_string()));
|
|
223
328
|
match &self.response {
|
|
224
|
-
Ok(outcome) => Ok(InvokeOutcome {
|
|
329
|
+
Ok(outcome) => Ok(InvokeOutcome {
|
|
330
|
+
body: outcome.body.clone(),
|
|
331
|
+
function_error: outcome.function_error,
|
|
332
|
+
}),
|
|
225
333
|
Err(message) => anyhow::bail!("{message}"),
|
|
226
334
|
}
|
|
227
335
|
}
|
|
@@ -240,7 +348,12 @@ mod tests {
|
|
|
240
348
|
async fn delivers_and_invokes_the_computed_function_name_with_the_verb_args_payload_shape() {
|
|
241
349
|
let invoker = MockLambdaInvoker::answering(serde_json::json!({ "refusals": [] }), false);
|
|
242
350
|
|
|
243
|
-
let record = deliver(
|
|
351
|
+
let record = deliver(
|
|
352
|
+
&invoker,
|
|
353
|
+
&reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
|
|
354
|
+
)
|
|
355
|
+
.await
|
|
356
|
+
.unwrap();
|
|
244
357
|
|
|
245
358
|
assert!(record.delivered);
|
|
246
359
|
assert_eq!(record.reason, None);
|
|
@@ -262,7 +375,12 @@ mod tests {
|
|
|
262
375
|
false,
|
|
263
376
|
);
|
|
264
377
|
|
|
265
|
-
let record = deliver(
|
|
378
|
+
let record = deliver(
|
|
379
|
+
&invoker,
|
|
380
|
+
&reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
|
|
381
|
+
)
|
|
382
|
+
.await
|
|
383
|
+
.unwrap();
|
|
266
384
|
|
|
267
385
|
assert!(!record.delivered);
|
|
268
386
|
assert_eq!(record.reason.as_deref(), Some("already under review"));
|
|
@@ -277,7 +395,12 @@ mod tests {
|
|
|
277
395
|
false,
|
|
278
396
|
);
|
|
279
397
|
|
|
280
|
-
let record = deliver(
|
|
398
|
+
let record = deliver(
|
|
399
|
+
&invoker,
|
|
400
|
+
&reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
|
|
401
|
+
)
|
|
402
|
+
.await
|
|
403
|
+
.unwrap();
|
|
281
404
|
|
|
282
405
|
assert!(record.delivered);
|
|
283
406
|
}
|
|
@@ -286,11 +409,21 @@ mod tests {
|
|
|
286
409
|
async fn a_function_error_response_is_a_hard_failure_not_a_delivery_record() {
|
|
287
410
|
// Mirrors Ruby's `raise WiringError` on `function_error` — not a domain
|
|
288
411
|
// refusal, so it propagates rather than becoming a `delivered: false` record.
|
|
289
|
-
let invoker = MockLambdaInvoker::answering(
|
|
412
|
+
let invoker = MockLambdaInvoker::answering(
|
|
413
|
+
serde_json::json!({ "errorMessage": "unhandled panic" }),
|
|
414
|
+
true,
|
|
415
|
+
);
|
|
290
416
|
|
|
291
|
-
let outcome = deliver(
|
|
417
|
+
let outcome = deliver(
|
|
418
|
+
&invoker,
|
|
419
|
+
&reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
|
|
420
|
+
)
|
|
421
|
+
.await;
|
|
292
422
|
|
|
293
|
-
assert!(
|
|
423
|
+
assert!(
|
|
424
|
+
outcome.is_err(),
|
|
425
|
+
"a function_error response must propagate as Err, not a delivered:false record"
|
|
426
|
+
);
|
|
294
427
|
}
|
|
295
428
|
|
|
296
429
|
#[tokio::test]
|
|
@@ -313,20 +446,31 @@ mod tests {
|
|
|
313
446
|
|
|
314
447
|
impl FlakyLambdaInvoker {
|
|
315
448
|
fn failing_then_succeeding(fail_count: u32) -> Self {
|
|
316
|
-
Self {
|
|
449
|
+
Self {
|
|
450
|
+
fail_count: std::sync::atomic::AtomicU32::new(fail_count),
|
|
451
|
+
calls: Mutex::new(Vec::new()),
|
|
452
|
+
}
|
|
317
453
|
}
|
|
318
454
|
}
|
|
319
455
|
|
|
320
456
|
#[async_trait::async_trait]
|
|
321
457
|
impl LambdaInvoker for FlakyLambdaInvoker {
|
|
322
|
-
async fn invoke(
|
|
458
|
+
async fn invoke(
|
|
459
|
+
&self,
|
|
460
|
+
function_name: &str,
|
|
461
|
+
_payload: &str,
|
|
462
|
+
) -> anyhow::Result<InvokeOutcome> {
|
|
323
463
|
self.calls.lock().unwrap().push(function_name.to_string());
|
|
324
464
|
let remaining = self.fail_count.load(std::sync::atomic::Ordering::SeqCst);
|
|
325
465
|
if remaining > 0 {
|
|
326
|
-
self.fail_count
|
|
466
|
+
self.fail_count
|
|
467
|
+
.store(remaining - 1, std::sync::atomic::Ordering::SeqCst);
|
|
327
468
|
anyhow::bail!("ThrottlingException: rate exceeded");
|
|
328
469
|
}
|
|
329
|
-
Ok(InvokeOutcome {
|
|
470
|
+
Ok(InvokeOutcome {
|
|
471
|
+
body: serde_json::json!({ "refusals": [] }),
|
|
472
|
+
function_error: false,
|
|
473
|
+
})
|
|
330
474
|
}
|
|
331
475
|
}
|
|
332
476
|
|
|
@@ -334,12 +478,19 @@ mod tests {
|
|
|
334
478
|
async fn deliver_with_retry_rides_out_a_transient_fault_that_clears_before_attempts_run_out() {
|
|
335
479
|
let invoker = FlakyLambdaInvoker::failing_then_succeeding(MAX_DELIVERY_ATTEMPTS - 1);
|
|
336
480
|
|
|
337
|
-
let record = deliver_with_retry(
|
|
338
|
-
|
|
339
|
-
|
|
481
|
+
let record = deliver_with_retry(
|
|
482
|
+
&invoker,
|
|
483
|
+
&reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
|
|
484
|
+
)
|
|
485
|
+
.await
|
|
486
|
+
.expect("should succeed once the fault clears, within MAX_DELIVERY_ATTEMPTS");
|
|
340
487
|
|
|
341
488
|
assert!(record.delivered);
|
|
342
|
-
assert_eq!(
|
|
489
|
+
assert_eq!(
|
|
490
|
+
invoker.calls.lock().unwrap().len(),
|
|
491
|
+
MAX_DELIVERY_ATTEMPTS as usize,
|
|
492
|
+
"should have retried exactly up to the successful attempt"
|
|
493
|
+
);
|
|
343
494
|
}
|
|
344
495
|
|
|
345
496
|
#[tokio::test]
|
|
@@ -351,28 +502,43 @@ mod tests {
|
|
|
351
502
|
false,
|
|
352
503
|
);
|
|
353
504
|
|
|
354
|
-
let record = deliver_with_retry(
|
|
355
|
-
|
|
356
|
-
|
|
505
|
+
let record = deliver_with_retry(
|
|
506
|
+
&invoker,
|
|
507
|
+
&reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
|
|
508
|
+
)
|
|
509
|
+
.await
|
|
510
|
+
.expect("a domain-side refusal is Ok, not a DeliveryFailure");
|
|
357
511
|
|
|
358
512
|
assert!(!record.delivered);
|
|
359
|
-
assert_eq!(
|
|
513
|
+
assert_eq!(
|
|
514
|
+
invoker.calls.lock().unwrap().len(),
|
|
515
|
+
1,
|
|
516
|
+
"a clean refusal should never be retried"
|
|
517
|
+
);
|
|
360
518
|
}
|
|
361
519
|
|
|
362
520
|
#[tokio::test]
|
|
363
|
-
async fn deliver_with_retry_gives_up_after_max_attempts_and_carries_everything_a_dead_letter_needs(
|
|
521
|
+
async fn deliver_with_retry_gives_up_after_max_attempts_and_carries_everything_a_dead_letter_needs(
|
|
522
|
+
) {
|
|
364
523
|
let invoker = MockLambdaInvoker::failing("ResourceNotFoundException: function not found");
|
|
365
524
|
|
|
366
|
-
let failure =
|
|
367
|
-
.
|
|
368
|
-
|
|
525
|
+
let failure =
|
|
526
|
+
deliver_with_retry(&invoker, &reaction("Notifications", "Notifications.Send"))
|
|
527
|
+
.await
|
|
528
|
+
.expect_err("a persistent fault should exhaust every attempt and fail");
|
|
369
529
|
|
|
370
530
|
assert_eq!(failure.policy, "ReviewOnFreeze");
|
|
371
531
|
assert_eq!(failure.target_domain, "Notifications");
|
|
372
532
|
assert_eq!(failure.target_verb, "Notifications.Send");
|
|
373
|
-
assert_eq!(
|
|
533
|
+
assert_eq!(
|
|
534
|
+
failure.payload,
|
|
535
|
+
serde_json::json!({ "number": { "value": "acct-1" } })
|
|
536
|
+
);
|
|
374
537
|
assert_eq!(failure.attempts, MAX_DELIVERY_ATTEMPTS);
|
|
375
538
|
assert!(format!("{:#}", failure.error).contains("ResourceNotFoundException"));
|
|
376
|
-
assert_eq!(
|
|
539
|
+
assert_eq!(
|
|
540
|
+
invoker.calls.lock().unwrap().len(),
|
|
541
|
+
MAX_DELIVERY_ATTEMPTS as usize
|
|
542
|
+
);
|
|
377
543
|
}
|
|
378
544
|
}
|
data/rust/host/src/lib.rs
CHANGED
|
@@ -2,20 +2,32 @@
|
|
|
2
2
|
//! Commerce (payments, checkout, newsletter, registrations, mail) plugs in through [`extension::HostExtension`].
|
|
3
3
|
|
|
4
4
|
pub mod api;
|
|
5
|
-
pub mod approval;
|
|
6
5
|
pub mod auth;
|
|
6
|
+
mod b64;
|
|
7
7
|
pub mod boot;
|
|
8
8
|
pub mod checkout;
|
|
9
|
+
mod clock;
|
|
9
10
|
pub mod commerce_ir;
|
|
11
|
+
mod crypto;
|
|
10
12
|
pub mod dispatch;
|
|
11
13
|
pub mod expr_json;
|
|
14
|
+
#[cfg(test)]
|
|
15
|
+
#[path = "boundary_fuzz/expr_json.rs"]
|
|
16
|
+
pub mod expr_json_fuzz;
|
|
12
17
|
pub mod extension;
|
|
18
|
+
mod http;
|
|
19
|
+
// Generated: its static regexes are literals that compile, and the generator always emits
|
|
20
|
+
// `r#"…"#`, so the lints below are allowed here.
|
|
21
|
+
#[allow(clippy::expect_used, clippy::needless_raw_string_hashes)]
|
|
13
22
|
pub mod field_hints;
|
|
14
23
|
#[cfg(test)]
|
|
15
24
|
pub mod fuzz_support;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
pub mod host;
|
|
26
|
+
// The one `Hecks::Naming` port, compiled from `hecks-codegen`'s source: the host and the codegen
|
|
27
|
+
// are independent Cargo projects, so the file is shared by path rather than by dependency.
|
|
28
|
+
#[allow(dead_code)]
|
|
29
|
+
#[path = "../../codegen/src/hecks_naming.rs"]
|
|
30
|
+
pub mod hecks_naming;
|
|
19
31
|
pub mod ir;
|
|
20
32
|
pub mod journal;
|
|
21
33
|
pub mod lambda_client;
|
data/rust/host/src/log.rs
CHANGED
|
@@ -29,8 +29,16 @@ pub fn phase(name: &'static str) -> Phase {
|
|
|
29
29
|
|
|
30
30
|
/// Starts a boot phase and logs its `start` line.
|
|
31
31
|
pub fn phase_with(name: &'static str, fields: Value) -> Phase {
|
|
32
|
-
emit(
|
|
33
|
-
|
|
32
|
+
emit(
|
|
33
|
+
"info",
|
|
34
|
+
"boot_phase",
|
|
35
|
+
phase_fields(name, "start", &fields, None),
|
|
36
|
+
);
|
|
37
|
+
Phase {
|
|
38
|
+
name,
|
|
39
|
+
fields,
|
|
40
|
+
started: std::time::Instant::now(),
|
|
41
|
+
}
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
impl Phase {
|
|
@@ -46,7 +54,11 @@ impl Phase {
|
|
|
46
54
|
if let (Value::Object(base), Value::Object(extra)) = (&mut fields, outcome) {
|
|
47
55
|
base.extend(extra);
|
|
48
56
|
}
|
|
49
|
-
emit(
|
|
57
|
+
emit(
|
|
58
|
+
"info",
|
|
59
|
+
"boot_phase",
|
|
60
|
+
phase_fields(self.name, "end", &fields, Some(elapsed_ms)),
|
|
61
|
+
);
|
|
50
62
|
elapsed_ms
|
|
51
63
|
}
|
|
52
64
|
}
|
|
@@ -113,22 +125,61 @@ mod tests {
|
|
|
113
125
|
|
|
114
126
|
#[test]
|
|
115
127
|
fn a_phase_line_names_the_phase_and_an_end_line_carries_elapsed_ms() {
|
|
116
|
-
let start = render(
|
|
128
|
+
let start = render(
|
|
129
|
+
"info",
|
|
130
|
+
"boot_phase",
|
|
131
|
+
phase_fields(
|
|
132
|
+
"audit_aggregate",
|
|
133
|
+
"start",
|
|
134
|
+
&json!({"aggregate": "registration"}),
|
|
135
|
+
None,
|
|
136
|
+
),
|
|
137
|
+
);
|
|
117
138
|
let parsed: Value = serde_json::from_str(&start).unwrap();
|
|
118
139
|
assert_eq!(parsed["msg"], "boot_phase");
|
|
119
|
-
assert_eq!(
|
|
140
|
+
assert_eq!(
|
|
141
|
+
(parsed["phase"].as_str(), parsed["event"].as_str()),
|
|
142
|
+
(Some("audit_aggregate"), Some("start"))
|
|
143
|
+
);
|
|
120
144
|
assert_eq!(parsed["aggregate"], "registration");
|
|
121
|
-
assert!(
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
assert!(
|
|
146
|
+
parsed.get("elapsed_ms").is_none(),
|
|
147
|
+
"a start line has no duration yet"
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
let end = render(
|
|
151
|
+
"info",
|
|
152
|
+
"boot_phase",
|
|
153
|
+
phase_fields(
|
|
154
|
+
"audit_aggregate",
|
|
155
|
+
"end",
|
|
156
|
+
&json!({"aggregate": "registration"}),
|
|
157
|
+
Some(42),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
124
160
|
let parsed: Value = serde_json::from_str(&end).unwrap();
|
|
125
|
-
assert_eq!(
|
|
161
|
+
assert_eq!(
|
|
162
|
+
(parsed["event"].as_str(), parsed["elapsed_ms"].as_u64()),
|
|
163
|
+
(Some("end"), Some(42))
|
|
164
|
+
);
|
|
126
165
|
}
|
|
127
166
|
|
|
128
167
|
#[test]
|
|
129
168
|
fn a_phase_field_cannot_overwrite_the_phase_or_event() {
|
|
130
|
-
let fields = phase_fields(
|
|
131
|
-
|
|
169
|
+
let fields = phase_fields(
|
|
170
|
+
"connect",
|
|
171
|
+
"start",
|
|
172
|
+
&json!({"phase": "other", "event": "end", "era": 7}),
|
|
173
|
+
None,
|
|
174
|
+
);
|
|
175
|
+
assert_eq!(
|
|
176
|
+
(
|
|
177
|
+
fields["phase"].as_str(),
|
|
178
|
+
fields["event"].as_str(),
|
|
179
|
+
fields["era"].as_u64()
|
|
180
|
+
),
|
|
181
|
+
(Some("connect"), Some("start"), Some(7))
|
|
182
|
+
);
|
|
132
183
|
}
|
|
133
184
|
|
|
134
185
|
#[test]
|