hecks 3.14.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/exe/hecks +1 -1
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +9 -16
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/app/responses.rb +3 -1
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value.rb +10 -21
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
- data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +306 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +151 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +574 -229
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +1037 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +555 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -516
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +22 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +244 -114
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +244 -148
- data/rust/src/kernel/mod.rs +47 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/present.rs +261 -0
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/rust/host/src/approval.rs +0 -652
|
@@ -6,7 +6,7 @@ use serde_json::Value;
|
|
|
6
6
|
|
|
7
7
|
/// The chapter that answers the guest newsletter signup, read from
|
|
8
8
|
/// `ir.json`'s own `newsletter` key. `None` when nothing provides it.
|
|
9
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
9
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
10
10
|
pub struct NewsletterProvider {
|
|
11
11
|
/// Chapter name, e.g. `Newsletter`.
|
|
12
12
|
pub provider: String,
|
|
@@ -58,9 +58,17 @@ fn declared_seconds(fact: &Value, key: &str) -> Option<u64> {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/// The declared seconds for `key`, else `legacy` with a single warning per process.
|
|
61
|
-
fn seconds_or_legacy(
|
|
61
|
+
fn seconds_or_legacy(
|
|
62
|
+
fact: &Value,
|
|
63
|
+
capability: &str,
|
|
64
|
+
key: &str,
|
|
65
|
+
legacy: u64,
|
|
66
|
+
warned: &std::sync::Once,
|
|
67
|
+
) -> u64 {
|
|
62
68
|
declared_seconds(fact, key).unwrap_or_else(|| {
|
|
63
|
-
warned.call_once(||
|
|
69
|
+
warned.call_once(|| {
|
|
70
|
+
eprintln!("{capability} capability declares no {key}; using built-in default");
|
|
71
|
+
});
|
|
64
72
|
legacy
|
|
65
73
|
})
|
|
66
74
|
}
|
|
@@ -68,7 +76,7 @@ fn seconds_or_legacy(fact: &Value, capability: &str, key: &str, legacy: u64, war
|
|
|
68
76
|
/// The checkout boundary's windows, read from `ir.json`'s own `checkout` key
|
|
69
77
|
/// (ADR 0098). Always present: a window the chapter does not declare takes its
|
|
70
78
|
/// labelled legacy default.
|
|
71
|
-
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
79
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
72
80
|
pub struct CheckoutWindows {
|
|
73
81
|
/// Seconds either side of now a signed webhook's timestamp may sit.
|
|
74
82
|
pub webhook_tolerance: i64,
|
|
@@ -92,9 +100,15 @@ pub fn checkout_windows(domain_ir: &Value) -> CheckoutWindows {
|
|
|
92
100
|
static TOLERANCE_WARNED: std::sync::Once = std::sync::Once::new();
|
|
93
101
|
static HOLD_WARNED: std::sync::Once = std::sync::Once::new();
|
|
94
102
|
let fact = domain_ir.get("checkout").unwrap_or(&Value::Null);
|
|
95
|
-
let seconds = |key, legacy: i64, warned|
|
|
103
|
+
let seconds = |key, legacy: i64, warned| {
|
|
104
|
+
seconds_or_legacy(fact, "checkout", key, legacy as u64, warned) as i64
|
|
105
|
+
};
|
|
96
106
|
CheckoutWindows {
|
|
97
|
-
webhook_tolerance: seconds(
|
|
107
|
+
webhook_tolerance: seconds(
|
|
108
|
+
"webhook_tolerance",
|
|
109
|
+
LEGACY_WEBHOOK_TOLERANCE_SECS,
|
|
110
|
+
&TOLERANCE_WARNED,
|
|
111
|
+
),
|
|
98
112
|
session_hold: seconds("session_hold", LEGACY_SESSION_HOLD_SECS, &HOLD_WARNED),
|
|
99
113
|
}
|
|
100
114
|
}
|
|
@@ -107,9 +121,10 @@ pub fn checkout_windows_binding() -> CheckoutWindows {
|
|
|
107
121
|
/// The confirm-link and unsubscribe-link windows of the IR this host loaded:
|
|
108
122
|
/// `(confirm, unsubscribe)` seconds, legacy defaults without a `newsletter` fact.
|
|
109
123
|
pub fn newsletter_windows_binding() -> (u64, u64) {
|
|
110
|
-
ir().and_then(newsletter_provider)
|
|
111
|
-
|
|
112
|
-
.
|
|
124
|
+
ir().and_then(newsletter_provider).map_or(
|
|
125
|
+
(LEGACY_CONFIRM_WINDOW_SECS, LEGACY_UNSUBSCRIBE_WINDOW_SECS),
|
|
126
|
+
|p| (p.confirm_window, p.unsubscribe_window),
|
|
127
|
+
)
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
/// LEGACY DEFAULT: the states a new subscriber waits in, used while the
|
|
@@ -133,14 +148,29 @@ pub const LEGACY_LEFT: [&str; 1] = ["unsubscribed"];
|
|
|
133
148
|
/// resolved by the exporter. `None` when the fact omits it.
|
|
134
149
|
fn declared_states(fact: &Value, key: &str) -> Option<Vec<String>> {
|
|
135
150
|
let states = fact.get(key)?.as_array()?;
|
|
136
|
-
Some(
|
|
151
|
+
Some(
|
|
152
|
+
states
|
|
153
|
+
.iter()
|
|
154
|
+
.filter_map(|s| s.as_str().map(String::from))
|
|
155
|
+
.collect(),
|
|
156
|
+
)
|
|
137
157
|
}
|
|
138
158
|
|
|
139
159
|
/// The declared states for `key`, else `legacy` with a single warning per process.
|
|
140
|
-
fn states_or_legacy(
|
|
160
|
+
fn states_or_legacy(
|
|
161
|
+
fact: &Value,
|
|
162
|
+
key: &str,
|
|
163
|
+
legacy: &[&str],
|
|
164
|
+
warned: &std::sync::Once,
|
|
165
|
+
) -> Vec<String> {
|
|
141
166
|
declared_states(fact, key).unwrap_or_else(|| {
|
|
142
|
-
warned.call_once(||
|
|
143
|
-
|
|
167
|
+
warned.call_once(|| {
|
|
168
|
+
eprintln!("newsletter capability declares no {key}; using built-in default");
|
|
169
|
+
});
|
|
170
|
+
legacy
|
|
171
|
+
.iter()
|
|
172
|
+
.map(std::string::ToString::to_string)
|
|
173
|
+
.collect()
|
|
144
174
|
})
|
|
145
175
|
}
|
|
146
176
|
|
|
@@ -163,7 +193,9 @@ impl NewsletterProvider {
|
|
|
163
193
|
/// The state a subscriber is reported in when none is recorded yet: the
|
|
164
194
|
/// first awaiting-confirmation state.
|
|
165
195
|
pub fn initial_status(&self) -> &str {
|
|
166
|
-
self.awaiting_confirmation
|
|
196
|
+
self.awaiting_confirmation
|
|
197
|
+
.first()
|
|
198
|
+
.map_or("", String::as_str)
|
|
167
199
|
}
|
|
168
200
|
|
|
169
201
|
/// The prefix every subscriber's key in a `dispatch::read` `instances`
|
|
@@ -176,7 +208,8 @@ impl NewsletterProvider {
|
|
|
176
208
|
/// Whether the domain's own `aggregate`'s `command` declares an argument
|
|
177
209
|
/// named `field` — so a route only sends an argument the command declares.
|
|
178
210
|
pub fn command_declares(domain_ir: &Value, aggregate: &str, command: &str, field: &str) -> bool {
|
|
179
|
-
let named =
|
|
211
|
+
let named =
|
|
212
|
+
|value: &Value, name: &str| value.get("name").and_then(|n| n.as_str()) == Some(name);
|
|
180
213
|
let Some(aggregates) = domain_ir.get("aggregates").and_then(|a| a.as_array()) else {
|
|
181
214
|
return false;
|
|
182
215
|
};
|
|
@@ -199,10 +232,32 @@ pub fn newsletter_provider(domain_ir: &Value) -> Option<NewsletterProvider> {
|
|
|
199
232
|
static UNSUBSCRIBE_WINDOW_WARNED: std::sync::Once = std::sync::Once::new();
|
|
200
233
|
let fact = domain_ir.get("newsletter")?;
|
|
201
234
|
Some(NewsletterProvider {
|
|
202
|
-
confirm_window: seconds_or_legacy(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
235
|
+
confirm_window: seconds_or_legacy(
|
|
236
|
+
fact,
|
|
237
|
+
"newsletter",
|
|
238
|
+
"confirm_window",
|
|
239
|
+
LEGACY_CONFIRM_WINDOW_SECS,
|
|
240
|
+
&CONFIRM_WINDOW_WARNED,
|
|
241
|
+
),
|
|
242
|
+
unsubscribe_window: seconds_or_legacy(
|
|
243
|
+
fact,
|
|
244
|
+
"newsletter",
|
|
245
|
+
"unsubscribe_window",
|
|
246
|
+
LEGACY_UNSUBSCRIBE_WINDOW_SECS,
|
|
247
|
+
&UNSUBSCRIBE_WINDOW_WARNED,
|
|
248
|
+
),
|
|
249
|
+
awaiting_confirmation: states_or_legacy(
|
|
250
|
+
fact,
|
|
251
|
+
"awaiting_confirmation",
|
|
252
|
+
&LEGACY_AWAITING_CONFIRMATION,
|
|
253
|
+
&AWAITING_WARNED,
|
|
254
|
+
),
|
|
255
|
+
receives_issues: states_or_legacy(
|
|
256
|
+
fact,
|
|
257
|
+
"receives_issues",
|
|
258
|
+
&LEGACY_RECEIVES_ISSUES,
|
|
259
|
+
&RECEIVES_WARNED,
|
|
260
|
+
),
|
|
206
261
|
left: states_or_legacy(fact, "left", &LEGACY_LEFT, &LEFT_WARNED),
|
|
207
262
|
provider: fact.get("provider")?.as_str()?.to_string(),
|
|
208
263
|
subscribe: fact.get("subscribe")?.as_str()?.to_string(),
|
|
@@ -215,7 +270,7 @@ pub fn newsletter_provider(domain_ir: &Value) -> Option<NewsletterProvider> {
|
|
|
215
270
|
|
|
216
271
|
/// The chapter that declares `provides "newsletter_issues"`, read from
|
|
217
272
|
/// `ir.json`'s own `newsletter_issues` key. Omitted when nothing declares it.
|
|
218
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
273
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
219
274
|
pub struct NewsletterIssuesProvider {
|
|
220
275
|
/// Qualified send command, e.g. `Newsletter::Issue.Send`.
|
|
221
276
|
pub send_issue: String,
|
|
@@ -244,10 +299,9 @@ pub fn newsletter_issues_provider(domain_ir: &Value) -> Option<NewsletterIssuesP
|
|
|
244
299
|
})
|
|
245
300
|
}
|
|
246
301
|
|
|
247
|
-
|
|
248
302
|
/// The chapter that takes payments, read from `ir.json`'s own `payments`
|
|
249
303
|
/// key. `None` when nothing provides it.
|
|
250
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
304
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
251
305
|
pub struct PaymentsProvider {
|
|
252
306
|
/// Chapter name, e.g. `Payments`.
|
|
253
307
|
pub provider: String,
|
|
@@ -283,7 +337,8 @@ pub const LEGACY_LAPSE_REASON: &str = "checkout_expired";
|
|
|
283
337
|
/// the chapter predates `provides "registrations", registered_at: "Registration.requested_at"`.
|
|
284
338
|
/// Delete this (and the fallback in `registrations_provider`) once every shipped
|
|
285
339
|
/// bluebook declares it.
|
|
286
|
-
pub const LEGACY_REGISTERED_AT: [&str; 4] =
|
|
340
|
+
pub const LEGACY_REGISTERED_AT: [&str; 4] =
|
|
341
|
+
["created_at", "registered_at", "requested_at", "occurred_at"];
|
|
287
342
|
|
|
288
343
|
/// LEGACY DEFAULT: the seat-holding payment states used while the `payments`
|
|
289
344
|
/// capability fact carries no `holds_seat` list, because the Payments chapter
|
|
@@ -303,7 +358,12 @@ pub const LEGACY_FREES_SEAT: [&str; 1] = ["archived"];
|
|
|
303
358
|
/// lifecycle mark, resolved by the exporter. `None` when the fact omits it.
|
|
304
359
|
fn declared_holds_seat(fact: &Value) -> Option<Vec<String>> {
|
|
305
360
|
let states = fact.get("holds_seat")?.as_array()?;
|
|
306
|
-
Some(
|
|
361
|
+
Some(
|
|
362
|
+
states
|
|
363
|
+
.iter()
|
|
364
|
+
.filter_map(|s| s.as_str().map(String::from))
|
|
365
|
+
.collect(),
|
|
366
|
+
)
|
|
307
367
|
}
|
|
308
368
|
|
|
309
369
|
impl PaymentsProvider {
|
|
@@ -319,8 +379,13 @@ pub fn payments_provider(domain_ir: &Value) -> Option<PaymentsProvider> {
|
|
|
319
379
|
let aggregate = fact.get("aggregate")?.as_str()?.to_string();
|
|
320
380
|
let holds_seat = declared_holds_seat(fact).unwrap_or_else(|| {
|
|
321
381
|
static WARNED: std::sync::Once = std::sync::Once::new();
|
|
322
|
-
WARNED.call_once(||
|
|
323
|
-
|
|
382
|
+
WARNED.call_once(|| {
|
|
383
|
+
eprintln!("payments capability declares no holds_seat; using built-in default");
|
|
384
|
+
});
|
|
385
|
+
LEGACY_HOLDS_SEAT
|
|
386
|
+
.iter()
|
|
387
|
+
.map(std::string::ToString::to_string)
|
|
388
|
+
.collect()
|
|
324
389
|
});
|
|
325
390
|
Some(PaymentsProvider {
|
|
326
391
|
provider: fact.get("provider")?.as_str()?.to_string(),
|
|
@@ -329,17 +394,28 @@ pub fn payments_provider(domain_ir: &Value) -> Option<PaymentsProvider> {
|
|
|
329
394
|
failed: fact.get("failed")?.as_str()?.to_string(),
|
|
330
395
|
aggregate,
|
|
331
396
|
holds_seat,
|
|
332
|
-
lapse_reason: fact
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
397
|
+
lapse_reason: fact
|
|
398
|
+
.get("lapse_reason")
|
|
399
|
+
.and_then(|v| v.as_str())
|
|
400
|
+
.filter(|v| !v.is_empty())
|
|
401
|
+
.map_or_else(
|
|
402
|
+
|| {
|
|
403
|
+
static WARNED: std::sync::Once = std::sync::Once::new();
|
|
404
|
+
WARNED.call_once(|| {
|
|
405
|
+
eprintln!(
|
|
406
|
+
"payments capability declares no lapse_reason; using built-in default"
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
LEGACY_LAPSE_REASON.to_string()
|
|
410
|
+
},
|
|
411
|
+
String::from,
|
|
412
|
+
),
|
|
337
413
|
})
|
|
338
414
|
}
|
|
339
415
|
|
|
340
416
|
/// The chapter that answers scheduling sessions and taking registrations,
|
|
341
417
|
/// read from `ir.json`'s own `registrations` key. `None` when nothing provides it.
|
|
342
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
418
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
343
419
|
pub struct RegistrationsProvider {
|
|
344
420
|
/// Chapter name, e.g. `Studio`.
|
|
345
421
|
pub provider: String,
|
|
@@ -371,8 +447,14 @@ impl RegistrationsProvider {
|
|
|
371
447
|
request: format!("{domain}::Registration.Request"),
|
|
372
448
|
event_aggregate: format!("{domain}::Event"),
|
|
373
449
|
registration_aggregate: format!("{domain}::Registration"),
|
|
374
|
-
timestamp_keys: LEGACY_REGISTERED_AT
|
|
375
|
-
|
|
450
|
+
timestamp_keys: LEGACY_REGISTERED_AT
|
|
451
|
+
.iter()
|
|
452
|
+
.map(std::string::ToString::to_string)
|
|
453
|
+
.collect(),
|
|
454
|
+
frees_seat: LEGACY_FREES_SEAT
|
|
455
|
+
.iter()
|
|
456
|
+
.map(std::string::ToString::to_string)
|
|
457
|
+
.collect(),
|
|
376
458
|
}
|
|
377
459
|
}
|
|
378
460
|
|
|
@@ -399,15 +481,25 @@ impl RegistrationsProvider {
|
|
|
399
481
|
/// mark, resolved by the exporter. `None` when the fact omits it.
|
|
400
482
|
fn declared_frees_seat(fact: &Value) -> Option<Vec<String>> {
|
|
401
483
|
let states = fact.get("frees_seat")?.as_array()?;
|
|
402
|
-
Some(
|
|
484
|
+
Some(
|
|
485
|
+
states
|
|
486
|
+
.iter()
|
|
487
|
+
.filter_map(|s| s.as_str().map(String::from))
|
|
488
|
+
.collect(),
|
|
489
|
+
)
|
|
403
490
|
}
|
|
404
491
|
|
|
405
492
|
pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider> {
|
|
406
493
|
let fact = domain_ir.get("registrations")?;
|
|
407
494
|
let frees_seat = declared_frees_seat(fact).unwrap_or_else(|| {
|
|
408
495
|
static WARNED: std::sync::Once = std::sync::Once::new();
|
|
409
|
-
WARNED.call_once(||
|
|
410
|
-
|
|
496
|
+
WARNED.call_once(|| {
|
|
497
|
+
eprintln!("registrations capability declares no frees_seat; using built-in default");
|
|
498
|
+
});
|
|
499
|
+
LEGACY_FREES_SEAT
|
|
500
|
+
.iter()
|
|
501
|
+
.map(std::string::ToString::to_string)
|
|
502
|
+
.collect()
|
|
411
503
|
});
|
|
412
504
|
Some(RegistrationsProvider {
|
|
413
505
|
provider: fact.get("provider")?.as_str()?.to_string(),
|
|
@@ -415,14 +507,18 @@ pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider
|
|
|
415
507
|
request: fact.get("request")?.as_str()?.to_string(),
|
|
416
508
|
event_aggregate: fact.get("event_aggregate")?.as_str()?.to_string(),
|
|
417
509
|
registration_aggregate: fact.get("registration_aggregate")?.as_str()?.to_string(),
|
|
418
|
-
timestamp_keys:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
510
|
+
timestamp_keys: fact
|
|
511
|
+
.get("registered_at")
|
|
512
|
+
.and_then(|v| v.as_str())
|
|
513
|
+
.filter(|v| !v.is_empty())
|
|
514
|
+
.map_or_else(
|
|
515
|
+
|| {
|
|
516
|
+
static WARNED: std::sync::Once = std::sync::Once::new();
|
|
517
|
+
WARNED.call_once(|| eprintln!("registrations capability declares no registered_at; using built-in default"));
|
|
518
|
+
LEGACY_REGISTERED_AT.iter().map(std::string::ToString::to_string).collect()
|
|
519
|
+
},
|
|
520
|
+
|declared| vec![declared.to_string()],
|
|
521
|
+
),
|
|
426
522
|
frees_seat,
|
|
427
523
|
})
|
|
428
524
|
}
|
|
@@ -430,7 +526,8 @@ pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider
|
|
|
430
526
|
/// The registrations binding of the IR this host loaded, or the
|
|
431
527
|
/// conventional names for `domain` when the IR declares none.
|
|
432
528
|
pub fn registrations_binding(domain: &str) -> RegistrationsProvider {
|
|
433
|
-
ir().and_then(registrations_provider)
|
|
529
|
+
ir().and_then(registrations_provider)
|
|
530
|
+
.unwrap_or_else(|| RegistrationsProvider::conventional(domain))
|
|
434
531
|
}
|
|
435
532
|
|
|
436
533
|
/// One command of the payment-processor connection, by the role it plays —
|
|
@@ -448,7 +545,7 @@ pub enum ConnectionVerb {
|
|
|
448
545
|
|
|
449
546
|
/// The chapter that owns the business's payment-processor connection, read
|
|
450
547
|
/// from `ir.json`'s own `payment_connection` key. `None` when nothing provides it.
|
|
451
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
548
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
452
549
|
pub struct PaymentConnectionProvider {
|
|
453
550
|
/// Chapter name, e.g. `Studio`.
|
|
454
551
|
pub provider: String,
|
|
@@ -520,15 +617,18 @@ pub fn payment_connection_provider(domain_ir: &Value) -> Option<PaymentConnectio
|
|
|
520
617
|
/// The payment-connection binding of the IR this host loaded, or the
|
|
521
618
|
/// conventional names for `domain` when the IR declares none.
|
|
522
619
|
pub fn payment_connection_binding(domain: &str) -> PaymentConnectionProvider {
|
|
523
|
-
ir().and_then(payment_connection_provider)
|
|
620
|
+
ir().and_then(payment_connection_provider)
|
|
621
|
+
.unwrap_or_else(|| PaymentConnectionProvider::conventional(domain))
|
|
524
622
|
}
|
|
525
623
|
|
|
526
624
|
/// The checkout fixture's own payments binding, read from its committed
|
|
527
625
|
/// `ir.json` — what the checkout routes' tests dispatch against.
|
|
528
626
|
#[cfg(test)]
|
|
529
627
|
pub fn fixture_ir() -> Value {
|
|
530
|
-
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
531
|
-
|
|
628
|
+
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
629
|
+
.join("../src/generated/checkout_fixture/ir.json");
|
|
630
|
+
serde_json::from_str(&std::fs::read_to_string(path).expect("checkout_fixture ir.json"))
|
|
631
|
+
.expect("valid json")
|
|
532
632
|
}
|
|
533
633
|
|
|
534
634
|
/// The checkout fixture's own payments binding.
|
|
@@ -564,7 +664,7 @@ mod tests {
|
|
|
564
664
|
assert!(newsletter_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
565
665
|
}
|
|
566
666
|
|
|
567
|
-
fn newsletter_ir(marks: serde_json::Value) -> Value {
|
|
667
|
+
fn newsletter_ir(marks: &serde_json::Value) -> Value {
|
|
568
668
|
let mut fact = serde_json::json!({
|
|
569
669
|
"provider": "Newsletter",
|
|
570
670
|
"subscribe": "Newsletter::Subscriber.Subscribe",
|
|
@@ -581,7 +681,7 @@ mod tests {
|
|
|
581
681
|
|
|
582
682
|
#[test]
|
|
583
683
|
fn newsletter_provider_reads_the_marks_from_the_fact() {
|
|
584
|
-
let ir = newsletter_ir(serde_json::json!({
|
|
684
|
+
let ir = newsletter_ir(&serde_json::json!({
|
|
585
685
|
"awaiting_confirmation": ["invited"],
|
|
586
686
|
"receives_issues": ["active", "vip"],
|
|
587
687
|
"left": ["gone"]
|
|
@@ -600,7 +700,8 @@ mod tests {
|
|
|
600
700
|
|
|
601
701
|
#[test]
|
|
602
702
|
fn newsletter_provider_falls_back_to_the_legacy_defaults_without_marks() {
|
|
603
|
-
let provider =
|
|
703
|
+
let provider =
|
|
704
|
+
newsletter_provider(&newsletter_ir(&serde_json::json!({}))).expect("newsletter");
|
|
604
705
|
assert_eq!(provider.awaiting_confirmation, LEGACY_AWAITING_CONFIRMATION);
|
|
605
706
|
assert_eq!(provider.receives_issues, LEGACY_RECEIVES_ISSUES);
|
|
606
707
|
assert_eq!(provider.left, LEGACY_LEFT);
|
|
@@ -613,33 +714,68 @@ mod tests {
|
|
|
613
714
|
|
|
614
715
|
#[test]
|
|
615
716
|
fn newsletter_provider_reads_the_link_windows_from_the_fact() {
|
|
616
|
-
let ir = newsletter_ir(
|
|
717
|
+
let ir = newsletter_ir(
|
|
718
|
+
&serde_json::json!({ "confirm_window": 3600, "unsubscribe_window": 7200 }),
|
|
719
|
+
);
|
|
617
720
|
let provider = newsletter_provider(&ir).expect("newsletter");
|
|
618
|
-
assert_eq!(
|
|
721
|
+
assert_eq!(
|
|
722
|
+
(provider.confirm_window, provider.unsubscribe_window),
|
|
723
|
+
(3600, 7200)
|
|
724
|
+
);
|
|
619
725
|
}
|
|
620
726
|
|
|
621
727
|
#[test]
|
|
622
728
|
fn newsletter_provider_falls_back_to_the_legacy_windows_without_them() {
|
|
623
|
-
let provider =
|
|
729
|
+
let provider =
|
|
730
|
+
newsletter_provider(&newsletter_ir(&serde_json::json!({}))).expect("newsletter");
|
|
624
731
|
assert_eq!(provider.confirm_window, LEGACY_CONFIRM_WINDOW_SECS);
|
|
625
732
|
assert_eq!(provider.unsubscribe_window, LEGACY_UNSUBSCRIBE_WINDOW_SECS);
|
|
626
|
-
assert_eq!(
|
|
733
|
+
assert_eq!(
|
|
734
|
+
(LEGACY_CONFIRM_WINDOW_SECS, LEGACY_UNSUBSCRIBE_WINDOW_SECS),
|
|
735
|
+
(1_209_600, 63_072_000)
|
|
736
|
+
);
|
|
627
737
|
}
|
|
628
738
|
|
|
629
739
|
#[test]
|
|
630
740
|
fn checkout_windows_read_the_fact_and_fall_back_per_window() {
|
|
631
741
|
let declared = serde_json::json!({ "checkout": { "provider": "Checkout", "webhook_tolerance": 120, "session_hold": 2400 } });
|
|
632
|
-
assert_eq!(
|
|
633
|
-
|
|
634
|
-
|
|
742
|
+
assert_eq!(
|
|
743
|
+
checkout_windows(&declared),
|
|
744
|
+
CheckoutWindows {
|
|
745
|
+
webhook_tolerance: 120,
|
|
746
|
+
session_hold: 2400
|
|
747
|
+
}
|
|
748
|
+
);
|
|
749
|
+
let partial =
|
|
750
|
+
serde_json::json!({ "checkout": { "provider": "Checkout", "session_hold": 2400 } });
|
|
751
|
+
assert_eq!(
|
|
752
|
+
checkout_windows(&partial),
|
|
753
|
+
CheckoutWindows {
|
|
754
|
+
webhook_tolerance: LEGACY_WEBHOOK_TOLERANCE_SECS,
|
|
755
|
+
session_hold: 2400
|
|
756
|
+
}
|
|
757
|
+
);
|
|
635
758
|
let none = checkout_windows(&serde_json::json!({ "name": "Pizzas" }));
|
|
636
|
-
assert_eq!(
|
|
759
|
+
assert_eq!(
|
|
760
|
+
none,
|
|
761
|
+
CheckoutWindows {
|
|
762
|
+
webhook_tolerance: 300,
|
|
763
|
+
session_hold: 1800
|
|
764
|
+
}
|
|
765
|
+
);
|
|
637
766
|
}
|
|
638
767
|
|
|
639
768
|
#[test]
|
|
640
769
|
fn a_zero_or_non_numeric_window_is_not_a_declaration() {
|
|
641
|
-
let ir =
|
|
642
|
-
|
|
770
|
+
let ir =
|
|
771
|
+
serde_json::json!({ "checkout": { "webhook_tolerance": 0, "session_hold": "soon" } });
|
|
772
|
+
assert_eq!(
|
|
773
|
+
checkout_windows(&ir),
|
|
774
|
+
CheckoutWindows {
|
|
775
|
+
webhook_tolerance: 300,
|
|
776
|
+
session_hold: 1800
|
|
777
|
+
}
|
|
778
|
+
);
|
|
643
779
|
}
|
|
644
780
|
|
|
645
781
|
#[test]
|
|
@@ -676,7 +812,10 @@ mod tests {
|
|
|
676
812
|
let provider = payments_provider(&ir).expect("should find payments");
|
|
677
813
|
assert_eq!(provider.provider, "Payments");
|
|
678
814
|
assert_eq!(provider.initiate, "Payments::Payment.Initiate");
|
|
679
|
-
assert_eq!(
|
|
815
|
+
assert_eq!(
|
|
816
|
+
provider.succeeded,
|
|
817
|
+
"Payments::Payment.PaymentGateway.Succeeded"
|
|
818
|
+
);
|
|
680
819
|
assert_eq!(provider.failed, "Payments::Payment.PaymentGateway.Failed");
|
|
681
820
|
assert_eq!(provider.instance_prefix(), "Payments::Payment#");
|
|
682
821
|
assert!(payments_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
@@ -697,31 +836,46 @@ mod tests {
|
|
|
697
836
|
#[test]
|
|
698
837
|
fn payments_provider_reads_holds_seat_from_the_fact() {
|
|
699
838
|
let ir = payments_ir(Some(serde_json::json!(["pending", "paid"])));
|
|
700
|
-
assert_eq!(
|
|
701
|
-
|
|
839
|
+
assert_eq!(
|
|
840
|
+
declared_holds_seat(&ir["payments"]),
|
|
841
|
+
Some(vec!["pending".to_string(), "paid".to_string()])
|
|
842
|
+
);
|
|
843
|
+
assert_eq!(
|
|
844
|
+
payments_provider(&ir).expect("payments").holds_seat,
|
|
845
|
+
vec!["pending", "paid"]
|
|
846
|
+
);
|
|
702
847
|
}
|
|
703
848
|
|
|
704
849
|
#[test]
|
|
705
850
|
fn the_checkout_fixture_ir_carries_holds_seat_with_no_fallback() {
|
|
706
851
|
let ir = fixture_ir();
|
|
707
|
-
assert_eq!(
|
|
852
|
+
assert_eq!(
|
|
853
|
+
declared_holds_seat(&ir["payments"]),
|
|
854
|
+
Some(vec!["pending".to_string(), "succeeded".to_string()])
|
|
855
|
+
);
|
|
708
856
|
assert_eq!(fixture_payments().holds_seat, vec!["pending", "succeeded"]);
|
|
709
857
|
}
|
|
710
858
|
|
|
711
859
|
#[test]
|
|
712
860
|
fn payments_provider_falls_back_to_the_legacy_default_without_holds_seat() {
|
|
713
861
|
assert_eq!(declared_holds_seat(&payments_ir(None)["payments"]), None);
|
|
714
|
-
let held = payments_provider(&payments_ir(None))
|
|
862
|
+
let held = payments_provider(&payments_ir(None))
|
|
863
|
+
.expect("payments")
|
|
864
|
+
.holds_seat;
|
|
715
865
|
assert_eq!(held, LEGACY_HOLDS_SEAT);
|
|
716
866
|
}
|
|
717
867
|
|
|
718
868
|
#[test]
|
|
719
869
|
fn registrations_provider_reads_the_declared_capability() {
|
|
720
|
-
let provider = registrations_provider(&fixture_ir())
|
|
870
|
+
let provider = registrations_provider(&fixture_ir())
|
|
871
|
+
.expect("the checkout fixture provides registrations");
|
|
721
872
|
assert_eq!(provider.schedule, "CheckoutFixture::Event.Schedule");
|
|
722
873
|
assert_eq!(provider.request, "CheckoutFixture::Registration.Request");
|
|
723
874
|
assert_eq!(provider.event_prefix(), "CheckoutFixture::Event#");
|
|
724
|
-
assert_eq!(
|
|
875
|
+
assert_eq!(
|
|
876
|
+
provider.registration_prefix(),
|
|
877
|
+
"CheckoutFixture::Registration#"
|
|
878
|
+
);
|
|
725
879
|
assert_eq!(provider.request_target(), Some(("Registration", "Request")));
|
|
726
880
|
}
|
|
727
881
|
|
|
@@ -731,18 +885,29 @@ mod tests {
|
|
|
731
885
|
let conventional = RegistrationsProvider {
|
|
732
886
|
timestamp_keys: declared.timestamp_keys.clone(),
|
|
733
887
|
frees_seat: declared.frees_seat.clone(),
|
|
734
|
-
..RegistrationsProvider::conventional("CheckoutFixture")
|
|
888
|
+
..RegistrationsProvider::conventional("CheckoutFixture")
|
|
889
|
+
};
|
|
735
890
|
assert_eq!(declared, conventional);
|
|
736
891
|
}
|
|
737
892
|
|
|
738
893
|
#[test]
|
|
739
894
|
fn the_registration_timestamp_is_the_declared_attribute_or_the_legacy_guess() {
|
|
740
|
-
assert_eq!(
|
|
895
|
+
assert_eq!(
|
|
896
|
+
registrations_provider(&fixture_ir())
|
|
897
|
+
.expect("registrations")
|
|
898
|
+
.timestamp_keys,
|
|
899
|
+
vec!["requested_at"]
|
|
900
|
+
);
|
|
741
901
|
let undeclared = serde_json::json!({"registrations": {
|
|
742
902
|
"provider": "B", "schedule": "B::E.S", "request": "B::R.Q",
|
|
743
903
|
"event_aggregate": "B::E", "registration_aggregate": "B::R"
|
|
744
904
|
}});
|
|
745
|
-
assert_eq!(
|
|
905
|
+
assert_eq!(
|
|
906
|
+
registrations_provider(&undeclared)
|
|
907
|
+
.expect("registrations")
|
|
908
|
+
.timestamp_keys,
|
|
909
|
+
LEGACY_REGISTERED_AT
|
|
910
|
+
);
|
|
746
911
|
}
|
|
747
912
|
|
|
748
913
|
fn registrations_ir(frees_seat: Option<serde_json::Value>) -> Value {
|
|
@@ -759,21 +924,42 @@ mod tests {
|
|
|
759
924
|
#[test]
|
|
760
925
|
fn registrations_provider_reads_frees_seat_from_the_fact() {
|
|
761
926
|
let ir = registrations_ir(Some(serde_json::json!(["cancelled", "archived"])));
|
|
762
|
-
assert_eq!(
|
|
763
|
-
|
|
927
|
+
assert_eq!(
|
|
928
|
+
declared_frees_seat(&ir["registrations"]),
|
|
929
|
+
Some(vec!["cancelled".to_string(), "archived".to_string()])
|
|
930
|
+
);
|
|
931
|
+
assert_eq!(
|
|
932
|
+
registrations_provider(&ir)
|
|
933
|
+
.expect("registrations")
|
|
934
|
+
.frees_seat,
|
|
935
|
+
vec!["cancelled", "archived"]
|
|
936
|
+
);
|
|
764
937
|
}
|
|
765
938
|
|
|
766
939
|
#[test]
|
|
767
940
|
fn the_checkout_fixture_ir_carries_frees_seat_with_no_fallback() {
|
|
768
941
|
let ir = fixture_ir();
|
|
769
|
-
assert_eq!(
|
|
770
|
-
|
|
942
|
+
assert_eq!(
|
|
943
|
+
declared_frees_seat(&ir["registrations"]),
|
|
944
|
+
Some(vec!["archived".to_string(), "cancelled".to_string()])
|
|
945
|
+
);
|
|
946
|
+
assert_eq!(
|
|
947
|
+
registrations_provider(&ir)
|
|
948
|
+
.expect("registrations")
|
|
949
|
+
.frees_seat,
|
|
950
|
+
vec!["archived", "cancelled"]
|
|
951
|
+
);
|
|
771
952
|
}
|
|
772
953
|
|
|
773
954
|
#[test]
|
|
774
955
|
fn registrations_provider_falls_back_to_the_legacy_default_without_frees_seat() {
|
|
775
|
-
assert_eq!(
|
|
776
|
-
|
|
956
|
+
assert_eq!(
|
|
957
|
+
declared_frees_seat(®istrations_ir(None)["registrations"]),
|
|
958
|
+
None
|
|
959
|
+
);
|
|
960
|
+
let freed = registrations_provider(®istrations_ir(None))
|
|
961
|
+
.expect("registrations")
|
|
962
|
+
.frees_seat;
|
|
777
963
|
assert_eq!(freed, LEGACY_FREES_SEAT);
|
|
778
964
|
}
|
|
779
965
|
|
|
@@ -781,9 +967,15 @@ mod tests {
|
|
|
781
967
|
fn the_lapse_reason_is_the_declared_word_or_the_legacy_default() {
|
|
782
968
|
assert_eq!(fixture_payments().lapse_reason, "checkout_expired");
|
|
783
969
|
let mut ir = payments_ir(None);
|
|
784
|
-
assert_eq!(
|
|
970
|
+
assert_eq!(
|
|
971
|
+
payments_provider(&ir).expect("payments").lapse_reason,
|
|
972
|
+
LEGACY_LAPSE_REASON
|
|
973
|
+
);
|
|
785
974
|
ir["payments"]["lapse_reason"] = serde_json::json!("hold_lapsed");
|
|
786
|
-
assert_eq!(
|
|
975
|
+
assert_eq!(
|
|
976
|
+
payments_provider(&ir).expect("payments").lapse_reason,
|
|
977
|
+
"hold_lapsed"
|
|
978
|
+
);
|
|
787
979
|
}
|
|
788
980
|
|
|
789
981
|
#[test]
|
|
@@ -803,26 +995,48 @@ mod tests {
|
|
|
803
995
|
|
|
804
996
|
#[test]
|
|
805
997
|
fn registrations_provider_is_none_without_the_key() {
|
|
806
|
-
assert_eq!(
|
|
998
|
+
assert_eq!(
|
|
999
|
+
registrations_provider(&serde_json::json!({"name": "Pizzas"})),
|
|
1000
|
+
None
|
|
1001
|
+
);
|
|
807
1002
|
}
|
|
808
1003
|
|
|
809
1004
|
#[test]
|
|
810
1005
|
fn registrations_binding_falls_back_to_the_conventional_names_when_no_ir_is_loaded() {
|
|
811
|
-
assert_eq!(
|
|
1006
|
+
assert_eq!(
|
|
1007
|
+
registrations_binding("Shop"),
|
|
1008
|
+
RegistrationsProvider::conventional("Shop")
|
|
1009
|
+
);
|
|
812
1010
|
}
|
|
813
1011
|
|
|
814
1012
|
#[test]
|
|
815
1013
|
fn payment_connection_provider_reads_the_declared_capability() {
|
|
816
|
-
let provider = payment_connection_provider(&fixture_ir())
|
|
817
|
-
|
|
818
|
-
assert_eq!(
|
|
819
|
-
|
|
820
|
-
|
|
1014
|
+
let provider = payment_connection_provider(&fixture_ir())
|
|
1015
|
+
.expect("the checkout fixture provides payment_connection");
|
|
1016
|
+
assert_eq!(
|
|
1017
|
+
provider.verb(ConnectionVerb::Connect),
|
|
1018
|
+
"CheckoutFixture::PaymentConnection.Connect"
|
|
1019
|
+
);
|
|
1020
|
+
assert_eq!(
|
|
1021
|
+
provider.verb(ConnectionVerb::Enable),
|
|
1022
|
+
"CheckoutFixture::PaymentConnection.EnablePayments"
|
|
1023
|
+
);
|
|
1024
|
+
assert_eq!(
|
|
1025
|
+
provider.verb(ConnectionVerb::Disable),
|
|
1026
|
+
"CheckoutFixture::PaymentConnection.DisablePayments"
|
|
1027
|
+
);
|
|
1028
|
+
assert_eq!(
|
|
1029
|
+
provider.instance_prefix(),
|
|
1030
|
+
"CheckoutFixture::PaymentConnection#"
|
|
1031
|
+
);
|
|
821
1032
|
}
|
|
822
1033
|
|
|
823
1034
|
#[test]
|
|
824
1035
|
fn the_conventional_payment_connection_names_match_what_the_fixture_declares() {
|
|
825
|
-
assert_eq!(
|
|
1036
|
+
assert_eq!(
|
|
1037
|
+
payment_connection_provider(&fixture_ir()),
|
|
1038
|
+
Some(PaymentConnectionProvider::conventional("CheckoutFixture"))
|
|
1039
|
+
);
|
|
826
1040
|
}
|
|
827
1041
|
|
|
828
1042
|
#[test]
|
|
@@ -835,14 +1049,23 @@ mod tests {
|
|
|
835
1049
|
"disable": "Billing::Link.TurnOff", "aggregate": "Billing::Link"
|
|
836
1050
|
}});
|
|
837
1051
|
let provider = payment_connection_provider(&ir).expect("declared");
|
|
838
|
-
assert_eq!(
|
|
839
|
-
|
|
1052
|
+
assert_eq!(
|
|
1053
|
+
provider.verb(ConnectionVerb::Suspend),
|
|
1054
|
+
"Billing::Link.Pause"
|
|
1055
|
+
);
|
|
1056
|
+
assert_eq!(
|
|
1057
|
+
provider.verb(ConnectionVerb::Resume),
|
|
1058
|
+
"Billing::Link.Unpause"
|
|
1059
|
+
);
|
|
840
1060
|
assert_eq!(provider.instance_prefix(), "Billing::Link#");
|
|
841
1061
|
}
|
|
842
1062
|
|
|
843
1063
|
#[test]
|
|
844
1064
|
fn payment_connection_binding_falls_back_to_the_conventional_names_when_no_ir_is_loaded() {
|
|
845
|
-
assert_eq!(
|
|
1065
|
+
assert_eq!(
|
|
1066
|
+
payment_connection_binding("Shop"),
|
|
1067
|
+
PaymentConnectionProvider::conventional("Shop")
|
|
1068
|
+
);
|
|
846
1069
|
}
|
|
847
1070
|
|
|
848
1071
|
#[test]
|
|
@@ -856,10 +1079,25 @@ mod tests {
|
|
|
856
1079
|
{"name": "Event", "commands": [{"name": "Request", "attributes": [{"name": "slug"}]}]}
|
|
857
1080
|
]
|
|
858
1081
|
});
|
|
859
|
-
assert!(command_declares(
|
|
860
|
-
|
|
1082
|
+
assert!(command_declares(
|
|
1083
|
+
&ir,
|
|
1084
|
+
"Registration",
|
|
1085
|
+
"Request",
|
|
1086
|
+
"news_signup"
|
|
1087
|
+
));
|
|
1088
|
+
assert!(!command_declares(
|
|
1089
|
+
&ir,
|
|
1090
|
+
"Registration",
|
|
1091
|
+
"Cancel",
|
|
1092
|
+
"news_signup"
|
|
1093
|
+
));
|
|
861
1094
|
assert!(!command_declares(&ir, "Event", "Request", "news_signup"));
|
|
862
1095
|
assert!(!command_declares(&ir, "Registration", "Request", "phone"));
|
|
863
|
-
assert!(!command_declares(
|
|
1096
|
+
assert!(!command_declares(
|
|
1097
|
+
&serde_json::json!({"name": "Pizzas"}),
|
|
1098
|
+
"Registration",
|
|
1099
|
+
"Request",
|
|
1100
|
+
"news_signup"
|
|
1101
|
+
));
|
|
864
1102
|
}
|
|
865
1103
|
}
|