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
|
@@ -21,8 +21,20 @@ pub struct StoredKeys {
|
|
|
21
21
|
|
|
22
22
|
impl StoredKeys {
|
|
23
23
|
fn from_json(value: &Value) -> Self {
|
|
24
|
-
let text = |field: &str|
|
|
25
|
-
|
|
24
|
+
let text = |field: &str| {
|
|
25
|
+
value
|
|
26
|
+
.get(field)
|
|
27
|
+
.and_then(|v| v.as_str())
|
|
28
|
+
.unwrap_or_default()
|
|
29
|
+
.to_string()
|
|
30
|
+
};
|
|
31
|
+
Self {
|
|
32
|
+
secret_key: text("secret_key"),
|
|
33
|
+
publishable_key: text("publishable_key"),
|
|
34
|
+
webhook_secret: text("webhook_secret"),
|
|
35
|
+
webhook_endpoint_id: text("webhook_endpoint_id"),
|
|
36
|
+
saved_at: text("saved_at"),
|
|
37
|
+
}
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
fn to_json(&self) -> Value {
|
|
@@ -45,8 +57,16 @@ pub struct StoredDocument {
|
|
|
45
57
|
|
|
46
58
|
impl StoredDocument {
|
|
47
59
|
fn from_json(value: &Value) -> Self {
|
|
48
|
-
let entry = |mode: &str|
|
|
49
|
-
|
|
60
|
+
let entry = |mode: &str| {
|
|
61
|
+
value
|
|
62
|
+
.get(mode)
|
|
63
|
+
.filter(|v| v.is_object())
|
|
64
|
+
.map(StoredKeys::from_json)
|
|
65
|
+
};
|
|
66
|
+
Self {
|
|
67
|
+
test: entry("test"),
|
|
68
|
+
live: entry("live"),
|
|
69
|
+
}
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
fn to_json(&self) -> Value {
|
|
@@ -61,17 +81,29 @@ impl StoredDocument {
|
|
|
61
81
|
|
|
62
82
|
/// The entry saved for `mode` ("test" or "live").
|
|
63
83
|
pub fn mode(&self, mode: &str) -> Option<&StoredKeys> {
|
|
64
|
-
if mode == "live" {
|
|
84
|
+
if mode == "live" {
|
|
85
|
+
self.live.as_ref()
|
|
86
|
+
} else {
|
|
87
|
+
self.test.as_ref()
|
|
88
|
+
}
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
/// Replaces (or with `None` removes) the entry for `mode`.
|
|
68
92
|
pub fn set(&mut self, mode: &str, keys: Option<StoredKeys>) {
|
|
69
|
-
if mode == "live" {
|
|
93
|
+
if mode == "live" {
|
|
94
|
+
self.live = keys;
|
|
95
|
+
} else {
|
|
96
|
+
self.test = keys;
|
|
97
|
+
}
|
|
70
98
|
}
|
|
71
99
|
|
|
72
100
|
/// Every webhook signing secret saved, for either mode.
|
|
73
101
|
pub fn webhook_secrets(&self) -> impl Iterator<Item = &str> {
|
|
74
|
-
[self.test.as_ref(), self.live.as_ref()]
|
|
102
|
+
[self.test.as_ref(), self.live.as_ref()]
|
|
103
|
+
.into_iter()
|
|
104
|
+
.flatten()
|
|
105
|
+
.map(|k| k.webhook_secret.as_str())
|
|
106
|
+
.filter(|s| !s.is_empty())
|
|
75
107
|
}
|
|
76
108
|
}
|
|
77
109
|
|
|
@@ -94,7 +126,10 @@ pub struct AwsRawStore {
|
|
|
94
126
|
impl AwsRawStore {
|
|
95
127
|
/// A store over the secret named `secret_id`; nothing is fetched until the first use.
|
|
96
128
|
pub fn new(secret_id: String) -> Self {
|
|
97
|
-
Self {
|
|
129
|
+
Self {
|
|
130
|
+
secret_id,
|
|
131
|
+
fetcher: tokio::sync::OnceCell::new(),
|
|
132
|
+
}
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
async fn fetcher(&self) -> &AwsSecretFetcher {
|
|
@@ -105,11 +140,17 @@ impl AwsRawStore {
|
|
|
105
140
|
#[async_trait::async_trait]
|
|
106
141
|
impl RawStore for AwsRawStore {
|
|
107
142
|
async fn read(&self) -> anyhow::Result<Option<String>> {
|
|
108
|
-
self.fetcher()
|
|
143
|
+
self.fetcher()
|
|
144
|
+
.await
|
|
145
|
+
.fetch_secret_string_if_present(&self.secret_id)
|
|
146
|
+
.await
|
|
109
147
|
}
|
|
110
148
|
|
|
111
149
|
async fn write(&self, document: &str) -> anyhow::Result<()> {
|
|
112
|
-
self.fetcher()
|
|
150
|
+
self.fetcher()
|
|
151
|
+
.await
|
|
152
|
+
.put_secret_string(&self.secret_id, document)
|
|
153
|
+
.await
|
|
113
154
|
}
|
|
114
155
|
}
|
|
115
156
|
|
|
@@ -123,12 +164,21 @@ pub struct KeyStore {
|
|
|
123
164
|
impl KeyStore {
|
|
124
165
|
/// A cached view over `raw`, starting with an empty cache.
|
|
125
166
|
pub fn new(raw: Arc<dyn RawStore>) -> Self {
|
|
126
|
-
Self {
|
|
167
|
+
Self {
|
|
168
|
+
raw,
|
|
169
|
+
cache: Mutex::new(None),
|
|
170
|
+
}
|
|
127
171
|
}
|
|
128
172
|
|
|
129
173
|
fn cached(&self) -> Option<Arc<StoredDocument>> {
|
|
130
|
-
let cache = self
|
|
131
|
-
|
|
174
|
+
let cache = self
|
|
175
|
+
.cache
|
|
176
|
+
.lock()
|
|
177
|
+
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
178
|
+
cache
|
|
179
|
+
.as_ref()
|
|
180
|
+
.filter(|(at, _)| at.elapsed() < CACHE_TTL)
|
|
181
|
+
.map(|(_, document)| document.clone())
|
|
132
182
|
}
|
|
133
183
|
|
|
134
184
|
/// The document, from the cache when it is under a minute old.
|
|
@@ -143,11 +193,18 @@ impl KeyStore {
|
|
|
143
193
|
pub async fn refresh(&self) -> anyhow::Result<Arc<StoredDocument>> {
|
|
144
194
|
let text = self.raw.read().await?;
|
|
145
195
|
let document = match text {
|
|
146
|
-
Some(text) => StoredDocument::from_json(
|
|
196
|
+
Some(text) => StoredDocument::from_json(
|
|
197
|
+
&serde_json::from_str::<Value>(&text)
|
|
198
|
+
.map_err(|_| anyhow::anyhow!("the stored payment keys are not valid JSON"))?,
|
|
199
|
+
),
|
|
147
200
|
None => StoredDocument::default(),
|
|
148
201
|
};
|
|
149
202
|
let document = Arc::new(document);
|
|
150
|
-
*self
|
|
203
|
+
*self
|
|
204
|
+
.cache
|
|
205
|
+
.lock()
|
|
206
|
+
.unwrap_or_else(std::sync::PoisonError::into_inner) =
|
|
207
|
+
Some((Instant::now(), document.clone()));
|
|
151
208
|
Ok(document)
|
|
152
209
|
}
|
|
153
210
|
|
|
@@ -165,40 +222,59 @@ impl KeyStore {
|
|
|
165
222
|
|
|
166
223
|
/// Forgets the cached document.
|
|
167
224
|
pub fn invalidate(&self) {
|
|
168
|
-
*self
|
|
225
|
+
*self
|
|
226
|
+
.cache
|
|
227
|
+
.lock()
|
|
228
|
+
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
|
|
169
229
|
}
|
|
170
230
|
}
|
|
171
231
|
|
|
172
232
|
/// The secret's name from a raw `PAYMENTS_ACCOUNT_SECRET_ID` value, or `None`
|
|
173
233
|
/// when it is unset or blank.
|
|
174
234
|
fn configured_secret_id(configured: Option<&str>) -> Option<String> {
|
|
175
|
-
configured
|
|
235
|
+
configured
|
|
236
|
+
.map(str::trim)
|
|
237
|
+
.filter(|id| !id.is_empty())
|
|
238
|
+
.map(String::from)
|
|
176
239
|
}
|
|
177
240
|
|
|
178
241
|
/// Whether this process runs as a container task or a Lambda function on AWS.
|
|
179
242
|
fn on_aws() -> bool {
|
|
180
|
-
["ECS_CONTAINER_METADATA_URI_V4", "AWS_LAMBDA_FUNCTION_NAME"]
|
|
243
|
+
["ECS_CONTAINER_METADATA_URI_V4", "AWS_LAMBDA_FUNCTION_NAME"]
|
|
244
|
+
.iter()
|
|
245
|
+
.any(|name| std::env::var(name).is_ok_and(|v| !v.is_empty()))
|
|
181
246
|
}
|
|
182
247
|
|
|
183
248
|
/// The message that stops the boot when checkout is enabled on AWS without a
|
|
184
249
|
/// secret to keep the business's keys in; `None` when the boot may go on.
|
|
185
|
-
pub fn refusal_at_boot(
|
|
250
|
+
pub fn refusal_at_boot(
|
|
251
|
+
checkout_enabled: bool,
|
|
252
|
+
on_aws: bool,
|
|
253
|
+
configured: Option<&str>,
|
|
254
|
+
) -> Option<String> {
|
|
186
255
|
let missing = checkout_enabled && on_aws && configured_secret_id(configured).is_none();
|
|
187
256
|
missing.then(|| format!("{SECRET_ID_VARIABLE} is required on AWS when checkout is enabled: name the Secrets Manager secret that holds the business's payment keys"))
|
|
188
257
|
}
|
|
189
258
|
|
|
190
259
|
/// `refusal_at_boot` for this process's environment.
|
|
191
260
|
pub fn check_boot(checkout_enabled: bool) -> Result<(), String> {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
261
|
+
refusal_at_boot(
|
|
262
|
+
checkout_enabled,
|
|
263
|
+
on_aws(),
|
|
264
|
+
std::env::var(SECRET_ID_VARIABLE).ok().as_deref(),
|
|
265
|
+
)
|
|
266
|
+
.map_or(Ok(()), Err)
|
|
196
267
|
}
|
|
197
268
|
|
|
198
269
|
/// The process-wide store, built once so its cache is shared by every request.
|
|
199
270
|
pub fn default_store() -> Option<Arc<KeyStore>> {
|
|
200
271
|
static STORE: OnceLock<Option<Arc<KeyStore>>> = OnceLock::new();
|
|
201
|
-
STORE
|
|
272
|
+
STORE
|
|
273
|
+
.get_or_init(|| {
|
|
274
|
+
configured_secret_id(std::env::var(SECRET_ID_VARIABLE).ok().as_deref())
|
|
275
|
+
.map(|id| Arc::new(KeyStore::new(Arc::new(AwsRawStore::new(id)))))
|
|
276
|
+
})
|
|
277
|
+
.clone()
|
|
202
278
|
}
|
|
203
279
|
|
|
204
280
|
/// `YYYY-MM-DDTHH:MM:SSZ` for a Unix time in seconds (proleptic Gregorian,
|
|
@@ -208,18 +284,33 @@ pub fn utc_timestamp(seconds: u64) -> String {
|
|
|
208
284
|
let z = days + 719_468;
|
|
209
285
|
let era = z.div_euclid(146_097);
|
|
210
286
|
let day_of_era = z.rem_euclid(146_097);
|
|
211
|
-
let year_of_era =
|
|
287
|
+
let year_of_era =
|
|
288
|
+
(day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
|
|
212
289
|
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
|
|
213
290
|
let month_shifted = (5 * day_of_year + 2) / 153;
|
|
214
291
|
let day = day_of_year - (153 * month_shifted + 2) / 5 + 1;
|
|
215
|
-
let month = if month_shifted < 10 {
|
|
292
|
+
let month = if month_shifted < 10 {
|
|
293
|
+
month_shifted + 3
|
|
294
|
+
} else {
|
|
295
|
+
month_shifted - 9
|
|
296
|
+
};
|
|
216
297
|
let year = year_of_era + era * 400 + i64::from(month <= 2);
|
|
217
|
-
format!(
|
|
298
|
+
format!(
|
|
299
|
+
"{year:04}-{month:02}-{day:02}T{:02}:{:02}:{:02}Z",
|
|
300
|
+
rest / 3_600,
|
|
301
|
+
rest % 3_600 / 60,
|
|
302
|
+
rest % 60
|
|
303
|
+
)
|
|
218
304
|
}
|
|
219
305
|
|
|
220
306
|
/// The current time as `utc_timestamp` writes it.
|
|
221
307
|
pub fn now_timestamp() -> String {
|
|
222
|
-
utc_timestamp(
|
|
308
|
+
utc_timestamp(
|
|
309
|
+
std::time::SystemTime::now()
|
|
310
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
311
|
+
.unwrap_or_default()
|
|
312
|
+
.as_secs(),
|
|
313
|
+
)
|
|
223
314
|
}
|
|
224
315
|
|
|
225
316
|
/// An in-memory stand-in for Secrets Manager that counts its calls, for the
|
|
@@ -239,15 +330,23 @@ pub(crate) struct MemoryStore {
|
|
|
239
330
|
impl RawStore for MemoryStore {
|
|
240
331
|
async fn read(&self) -> anyhow::Result<Option<String>> {
|
|
241
332
|
self.reads.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
242
|
-
Ok(self
|
|
333
|
+
Ok(self
|
|
334
|
+
.document
|
|
335
|
+
.lock()
|
|
336
|
+
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
337
|
+
.clone())
|
|
243
338
|
}
|
|
244
339
|
|
|
245
340
|
async fn write(&self, document: &str) -> anyhow::Result<()> {
|
|
246
|
-
self.writes
|
|
341
|
+
self.writes
|
|
342
|
+
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
247
343
|
if self.fail_writes.load(std::sync::atomic::Ordering::SeqCst) {
|
|
248
344
|
return Err(anyhow::anyhow!("writing the secret failed"));
|
|
249
345
|
}
|
|
250
|
-
*self
|
|
346
|
+
*self
|
|
347
|
+
.document
|
|
348
|
+
.lock()
|
|
349
|
+
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(document.to_string());
|
|
251
350
|
Ok(())
|
|
252
351
|
}
|
|
253
352
|
}
|
|
@@ -258,7 +357,13 @@ mod tests {
|
|
|
258
357
|
use std::sync::atomic::Ordering;
|
|
259
358
|
|
|
260
359
|
fn keys(secret: &str) -> StoredKeys {
|
|
261
|
-
StoredKeys {
|
|
360
|
+
StoredKeys {
|
|
361
|
+
secret_key: secret.into(),
|
|
362
|
+
publishable_key: "pk_test_x".into(),
|
|
363
|
+
webhook_secret: "whsec_x".into(),
|
|
364
|
+
webhook_endpoint_id: "we_1".into(),
|
|
365
|
+
saved_at: "2026-09-26T05:00:00Z".into(),
|
|
366
|
+
}
|
|
262
367
|
}
|
|
263
368
|
|
|
264
369
|
#[tokio::test]
|
|
@@ -267,12 +372,23 @@ mod tests {
|
|
|
267
372
|
let store = KeyStore::new(raw.clone());
|
|
268
373
|
assert!(store.document().await.unwrap().test.is_none());
|
|
269
374
|
assert!(store.document().await.unwrap().test.is_none());
|
|
270
|
-
assert_eq!(
|
|
271
|
-
|
|
272
|
-
|
|
375
|
+
assert_eq!(
|
|
376
|
+
raw.reads.load(Ordering::SeqCst),
|
|
377
|
+
1,
|
|
378
|
+
"the second read was served from the cache"
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
store
|
|
382
|
+
.update(|doc| doc.set("test", Some(keys("rk_test_one"))))
|
|
383
|
+
.await
|
|
384
|
+
.unwrap();
|
|
273
385
|
let reads_after_update = raw.reads.load(Ordering::SeqCst);
|
|
274
386
|
let document = store.document().await.unwrap();
|
|
275
|
-
assert_eq!(
|
|
387
|
+
assert_eq!(
|
|
388
|
+
document.test.as_ref().unwrap().secret_key,
|
|
389
|
+
"rk_test_one",
|
|
390
|
+
"the write invalidated the cache"
|
|
391
|
+
);
|
|
276
392
|
assert_eq!(raw.reads.load(Ordering::SeqCst), reads_after_update + 1);
|
|
277
393
|
assert_eq!(raw.writes.load(Ordering::SeqCst), 1);
|
|
278
394
|
}
|
|
@@ -281,20 +397,38 @@ mod tests {
|
|
|
281
397
|
async fn the_stored_json_has_exactly_the_agreed_shape() {
|
|
282
398
|
let raw = Arc::new(MemoryStore::default());
|
|
283
399
|
let store = KeyStore::new(raw.clone());
|
|
284
|
-
store
|
|
285
|
-
|
|
400
|
+
store
|
|
401
|
+
.update(|doc| doc.set("live", Some(keys("rk_live_one"))))
|
|
402
|
+
.await
|
|
403
|
+
.unwrap();
|
|
404
|
+
let stored: serde_json::Value =
|
|
405
|
+
serde_json::from_str(raw.document.lock().unwrap().as_ref().unwrap()).unwrap();
|
|
286
406
|
assert!(stored.get("test").is_none(), "an unsaved mode has no entry");
|
|
287
407
|
let live = stored["live"].as_object().unwrap();
|
|
288
408
|
let mut names: Vec<&str> = live.keys().map(String::as_str).collect();
|
|
289
409
|
names.sort_unstable();
|
|
290
|
-
assert_eq!(
|
|
410
|
+
assert_eq!(
|
|
411
|
+
names,
|
|
412
|
+
[
|
|
413
|
+
"publishable_key",
|
|
414
|
+
"saved_at",
|
|
415
|
+
"secret_key",
|
|
416
|
+
"webhook_endpoint_id",
|
|
417
|
+
"webhook_secret"
|
|
418
|
+
]
|
|
419
|
+
);
|
|
291
420
|
}
|
|
292
421
|
|
|
293
422
|
#[tokio::test]
|
|
294
423
|
async fn an_unreadable_document_is_an_error_that_does_not_quote_it() {
|
|
295
424
|
let raw = Arc::new(MemoryStore::default());
|
|
296
425
|
*raw.document.lock().unwrap() = Some("rk_live_SECRET not json".to_string());
|
|
297
|
-
let error = KeyStore::new(raw)
|
|
426
|
+
let error = KeyStore::new(raw)
|
|
427
|
+
.document()
|
|
428
|
+
.await
|
|
429
|
+
.err()
|
|
430
|
+
.expect("an error")
|
|
431
|
+
.to_string();
|
|
298
432
|
assert!(!error.contains("rk_live_SECRET"), "{error}");
|
|
299
433
|
}
|
|
300
434
|
|
|
@@ -303,21 +437,39 @@ mod tests {
|
|
|
303
437
|
assert_eq!(configured_secret_id(None), None);
|
|
304
438
|
assert_eq!(configured_secret_id(Some("")), None);
|
|
305
439
|
assert_eq!(configured_secret_id(Some(" ")), None);
|
|
306
|
-
assert_eq!(
|
|
440
|
+
assert_eq!(
|
|
441
|
+
configured_secret_id(Some(" payments-keys ")),
|
|
442
|
+
Some("payments-keys".to_string())
|
|
443
|
+
);
|
|
307
444
|
}
|
|
308
445
|
|
|
309
446
|
#[test]
|
|
310
447
|
fn checkout_on_aws_without_a_secret_id_refuses_the_boot_and_names_the_variable() {
|
|
311
448
|
let message = refusal_at_boot(true, true, None).expect("refused");
|
|
312
449
|
assert!(message.contains("PAYMENTS_ACCOUNT_SECRET_ID"), "{message}");
|
|
313
|
-
assert!(
|
|
450
|
+
assert!(
|
|
451
|
+
refusal_at_boot(true, true, Some(" ")).is_some(),
|
|
452
|
+
"a blank value is unset"
|
|
453
|
+
);
|
|
314
454
|
}
|
|
315
455
|
|
|
316
456
|
#[test]
|
|
317
457
|
fn every_other_combination_boots() {
|
|
318
|
-
assert_eq!(
|
|
319
|
-
|
|
320
|
-
|
|
458
|
+
assert_eq!(
|
|
459
|
+
refusal_at_boot(true, true, Some("payments-keys")),
|
|
460
|
+
None,
|
|
461
|
+
"set: unchanged"
|
|
462
|
+
);
|
|
463
|
+
assert_eq!(
|
|
464
|
+
refusal_at_boot(true, false, None),
|
|
465
|
+
None,
|
|
466
|
+
"off AWS: only environment keys"
|
|
467
|
+
);
|
|
468
|
+
assert_eq!(
|
|
469
|
+
refusal_at_boot(false, true, None),
|
|
470
|
+
None,
|
|
471
|
+
"checkout off: payments are not served"
|
|
472
|
+
);
|
|
321
473
|
assert_eq!(refusal_at_boot(false, false, None), None);
|
|
322
474
|
}
|
|
323
475
|
|
|
@@ -24,7 +24,9 @@ pub fn default_roles() -> Vec<String> {
|
|
|
24
24
|
|
|
25
25
|
/// Parses the variable's value; `None` (unset) gives the default list.
|
|
26
26
|
pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
|
|
27
|
-
let Some(raw) = raw else {
|
|
27
|
+
let Some(raw) = raw else {
|
|
28
|
+
return Ok(default_roles());
|
|
29
|
+
};
|
|
28
30
|
let refuse = |why: &str| Err(format!("{VARIABLE} is invalid: {why}"));
|
|
29
31
|
let entries: Vec<&str> = raw.split(',').map(str::trim).collect();
|
|
30
32
|
if entries.len() > MAX_ENTRIES {
|
|
@@ -33,13 +35,21 @@ pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
|
|
|
33
35
|
for (index, entry) in entries.iter().enumerate() {
|
|
34
36
|
let position = index + 1;
|
|
35
37
|
if entry.is_empty() {
|
|
36
|
-
return refuse(&format!(
|
|
38
|
+
return refuse(&format!(
|
|
39
|
+
"entry {position} is empty; name at least one role and no blank entries"
|
|
40
|
+
));
|
|
37
41
|
}
|
|
38
42
|
if entry.len() > MAX_ENTRY_LENGTH {
|
|
39
|
-
return refuse(&format!(
|
|
43
|
+
return refuse(&format!(
|
|
44
|
+
"entry {position} is longer than {MAX_ENTRY_LENGTH} characters"
|
|
45
|
+
));
|
|
40
46
|
}
|
|
41
47
|
let starts_well = entry.starts_with(|c: char| c.is_ascii_alphanumeric());
|
|
42
|
-
if !starts_well
|
|
48
|
+
if !starts_well
|
|
49
|
+
|| !entry
|
|
50
|
+
.chars()
|
|
51
|
+
.all(|c| c.is_ascii_alphanumeric() || c == ' ' || c == '-')
|
|
52
|
+
{
|
|
43
53
|
return refuse(&format!("entry {position} is not a role name: letters, digits, spaces and hyphens only, starting with a letter or digit"));
|
|
44
54
|
}
|
|
45
55
|
}
|
|
@@ -48,7 +58,10 @@ pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
|
|
|
48
58
|
|
|
49
59
|
/// Reads the setting into `cell` the first time and never again, whatever the environment
|
|
50
60
|
/// does afterwards. `read` stands for the environment lookup.
|
|
51
|
-
pub fn read_once(
|
|
61
|
+
pub fn read_once(
|
|
62
|
+
cell: &OnceLock<Result<Vec<String>, String>>,
|
|
63
|
+
read: impl FnOnce() -> Option<String>,
|
|
64
|
+
) -> &Result<Vec<String>, String> {
|
|
52
65
|
cell.get_or_init(|| parse(read().as_deref()))
|
|
53
66
|
}
|
|
54
67
|
|
|
@@ -112,14 +125,33 @@ mod tests {
|
|
|
112
125
|
#[test]
|
|
113
126
|
fn no_wildcard_and_no_hierarchy() {
|
|
114
127
|
assert!(parse(Some("*")).is_err());
|
|
115
|
-
assert!(
|
|
128
|
+
assert!(
|
|
129
|
+
!manages(&roles(Some("Admin")), "Owner"),
|
|
130
|
+
"naming Admin does not admit Owner"
|
|
131
|
+
);
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
#[test]
|
|
119
135
|
fn an_invalid_setting_is_refused_naming_the_variable() {
|
|
120
136
|
let too_many = ["Role"; MAX_ENTRIES + 1].join(",");
|
|
121
137
|
let too_long = "A".repeat(MAX_ENTRY_LENGTH + 1);
|
|
122
|
-
for bad in [
|
|
138
|
+
for bad in [
|
|
139
|
+
"",
|
|
140
|
+
" ",
|
|
141
|
+
",",
|
|
142
|
+
"Owner,",
|
|
143
|
+
",Owner",
|
|
144
|
+
"Owner,,Admin",
|
|
145
|
+
"Own/er",
|
|
146
|
+
"Owner\nAdmin",
|
|
147
|
+
"Own\u{7}er",
|
|
148
|
+
"Admin;Owner",
|
|
149
|
+
"-Admin",
|
|
150
|
+
"Ad_min",
|
|
151
|
+
"Régisseur",
|
|
152
|
+
too_many.as_str(),
|
|
153
|
+
too_long.as_str(),
|
|
154
|
+
] {
|
|
123
155
|
let message = parse(Some(bad)).expect_err(bad);
|
|
124
156
|
assert!(message.starts_with(VARIABLE), "{message}");
|
|
125
157
|
}
|