hecks 3.14.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value.rb +13 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json} +1 -1
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +300 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +145 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +990 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +483 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -516
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +22 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +229 -112
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +230 -148
- data/rust/src/kernel/mod.rs +46 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +82 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/rust/host/src/approval.rs +0 -652
|
@@ -27,8 +27,10 @@ pub struct InMemoryRepository<T: Clone> {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
impl<T: Clone> InMemoryRepository<T> {
|
|
30
|
-
pub fn new() -> Self {
|
|
31
|
-
Self {
|
|
30
|
+
pub const fn new() -> Self {
|
|
31
|
+
Self {
|
|
32
|
+
records: BTreeMap::new(),
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/// `(id, record)` pairs; `Repository::all` drops the id, which the CLI `instances` dump needs.
|
|
@@ -46,7 +48,9 @@ impl<T: Clone> InMemoryRepository<T> {
|
|
|
46
48
|
&'a self,
|
|
47
49
|
to_json: impl Fn(&T) -> super::Json + 'a,
|
|
48
50
|
) -> impl Iterator<Item = (&'a String, &'a super::Json)> {
|
|
49
|
-
self.records
|
|
51
|
+
self.records
|
|
52
|
+
.iter()
|
|
53
|
+
.map(move |(id, slot)| (id, slot.json.get_or_init(|| to_json(&slot.record))))
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -56,11 +60,20 @@ impl<T: Clone> Repository<T> for InMemoryRepository<T> {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
fn save(&mut self, id: &str, record: T) {
|
|
59
|
-
self.records.insert(
|
|
63
|
+
self.records.insert(
|
|
64
|
+
id.to_string(),
|
|
65
|
+
Slot {
|
|
66
|
+
record,
|
|
67
|
+
json: OnceCell::new(),
|
|
68
|
+
},
|
|
69
|
+
);
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
fn all(&self) -> Vec<T> {
|
|
63
|
-
self.records
|
|
73
|
+
self.records
|
|
74
|
+
.values()
|
|
75
|
+
.map(|slot| slot.record.clone())
|
|
76
|
+
.collect()
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
fn count(&self) -> usize {
|
|
@@ -83,7 +96,12 @@ pub fn check_reference<T: Clone>(
|
|
|
83
96
|
}
|
|
84
97
|
// Wording goes through `RefusalSite` so it cannot drift from the Ruby table.
|
|
85
98
|
Err(super::Refusal::NotFound(
|
|
86
|
-
super::refusal_wording::NotFoundReferenceTargetMissingArgs {
|
|
99
|
+
super::refusal_wording::NotFoundReferenceTargetMissingArgs {
|
|
100
|
+
target,
|
|
101
|
+
heads,
|
|
102
|
+
key: value,
|
|
103
|
+
}
|
|
104
|
+
.render_args(),
|
|
87
105
|
))
|
|
88
106
|
}
|
|
89
107
|
|
|
@@ -104,7 +122,9 @@ pub trait AggregateScan {
|
|
|
104
122
|
/// `scan`, so a store that overrides only `scan` still answers; generated stores override
|
|
105
123
|
/// this to read their repositories' cached renderings.
|
|
106
124
|
fn scan_each(&self, aggregate: &str, visit: &mut dyn FnMut(&str, &super::Json)) -> bool {
|
|
107
|
-
let Some(entries) = self.scan(aggregate) else {
|
|
125
|
+
let Some(entries) = self.scan(aggregate) else {
|
|
126
|
+
return false;
|
|
127
|
+
};
|
|
108
128
|
for (id, record) in &entries {
|
|
109
129
|
visit(id, record);
|
|
110
130
|
}
|
|
@@ -168,7 +188,9 @@ pub fn entry_matches(
|
|
|
168
188
|
///
|
|
169
189
|
/// Shared because read models nest this wrapping at every level, not only the top row.
|
|
170
190
|
pub fn row_json(id: String, record: super::Json) -> super::Json {
|
|
171
|
-
let super::Json::Object(mut fields) = record else {
|
|
191
|
+
let super::Json::Object(mut fields) = record else {
|
|
192
|
+
return record;
|
|
193
|
+
};
|
|
172
194
|
fields.insert(0, ("id".to_string(), super::Json::Str(id)));
|
|
173
195
|
super::Json::Object(fields)
|
|
174
196
|
}
|
|
@@ -187,10 +209,16 @@ pub fn holds_role_via(
|
|
|
187
209
|
let Some(def) = assignments.and_then(|verb| super::named_query::find(queries, verb)) else {
|
|
188
210
|
return false;
|
|
189
211
|
};
|
|
190
|
-
let args = super::Json::Object(vec![(
|
|
191
|
-
|
|
212
|
+
let args = super::Json::Object(vec![(
|
|
213
|
+
"actor_id".to_string(),
|
|
214
|
+
super::Json::Str(actor_id.to_string()),
|
|
215
|
+
)]);
|
|
216
|
+
let Ok(rows) = super::named_query::run(store, def, &args, None) else {
|
|
217
|
+
return false;
|
|
218
|
+
};
|
|
192
219
|
rows.iter().any(|(_, record)| {
|
|
193
|
-
let matches_role =
|
|
220
|
+
let matches_role =
|
|
221
|
+
record.dig("role_name.value").and_then(super::Json::as_str) == Some(role);
|
|
194
222
|
let not_revoked = matches!(record.dig("ends_at"), None | Some(super::Json::Null));
|
|
195
223
|
matches_role && not_revoked
|
|
196
224
|
})
|
|
@@ -214,7 +242,9 @@ pub fn check_role_via(
|
|
|
214
242
|
queries: &[super::QueryDef],
|
|
215
243
|
assignments: Option<&str>,
|
|
216
244
|
) -> Result<(), super::Refusal> {
|
|
217
|
-
let attached = assignments
|
|
245
|
+
let attached = assignments
|
|
246
|
+
.and_then(|verb| super::named_query::find(queries, verb))
|
|
247
|
+
.is_some();
|
|
218
248
|
// An identified caller with no stated role is checked against what Governance assigned them: the
|
|
219
249
|
// caller need not claim a role it may not hold. Unchecked without a Governance provider, as ever.
|
|
220
250
|
if let (None, Some(role), Some(actor_id)) = (caller_role, command_role, caller_actor_id) {
|
|
@@ -222,10 +252,17 @@ pub fn check_role_via(
|
|
|
222
252
|
return Ok(());
|
|
223
253
|
}
|
|
224
254
|
return Err(super::Refusal::Unauthorized(
|
|
225
|
-
super::refusal_wording::UnauthorizedRoleMismatchArgs {
|
|
255
|
+
super::refusal_wording::UnauthorizedRoleMismatchArgs {
|
|
256
|
+
command: command_name,
|
|
257
|
+
role,
|
|
258
|
+
caller_role: "no assigned role",
|
|
259
|
+
}
|
|
260
|
+
.render_args(),
|
|
226
261
|
));
|
|
227
262
|
}
|
|
228
|
-
let (Some(caller), Some(role)) = (caller_role, command_role) else {
|
|
263
|
+
let (Some(caller), Some(role)) = (caller_role, command_role) else {
|
|
264
|
+
return Ok(());
|
|
265
|
+
};
|
|
229
266
|
|
|
230
267
|
let authorized = match caller_actor_id {
|
|
231
268
|
Some(actor_id) if attached => holds_role_via(store, queries, assignments, actor_id, role),
|
|
@@ -237,7 +274,12 @@ pub fn check_role_via(
|
|
|
237
274
|
}
|
|
238
275
|
// The refusal quotes the role the caller typed, not the one `holds_role_via` found (as Ruby).
|
|
239
276
|
Err(super::Refusal::Unauthorized(
|
|
240
|
-
super::refusal_wording::UnauthorizedRoleMismatchArgs {
|
|
277
|
+
super::refusal_wording::UnauthorizedRoleMismatchArgs {
|
|
278
|
+
command: command_name,
|
|
279
|
+
role,
|
|
280
|
+
caller_role: caller,
|
|
281
|
+
}
|
|
282
|
+
.render_args(),
|
|
241
283
|
))
|
|
242
284
|
}
|
|
243
285
|
|
|
@@ -257,12 +299,18 @@ mod filter_entries_none_in_state_tests {
|
|
|
257
299
|
impl AggregateScan for FakeStore {
|
|
258
300
|
fn scan(&self, aggregate: &str) -> Option<Vec<(String, Json)>> {
|
|
259
301
|
let bare = aggregate.strip_prefix(&format!("{}::", self.domain))?;
|
|
260
|
-
self.aggregates
|
|
302
|
+
self.aggregates
|
|
303
|
+
.iter()
|
|
304
|
+
.find(|(name, _)| *name == bare)
|
|
305
|
+
.map(|(_, entries)| entries.clone())
|
|
261
306
|
}
|
|
262
307
|
}
|
|
263
308
|
|
|
264
309
|
fn claim_record(state: &str) -> Json {
|
|
265
|
-
Json::obj(vec![(
|
|
310
|
+
Json::obj(vec![(
|
|
311
|
+
"state",
|
|
312
|
+
Json::obj(vec![("value", Json::str(state))]),
|
|
313
|
+
)])
|
|
266
314
|
}
|
|
267
315
|
|
|
268
316
|
fn assignment_record(claim_id: &str) -> Json {
|
|
@@ -298,8 +346,10 @@ mod filter_entries_none_in_state_tests {
|
|
|
298
346
|
&cross_domain,
|
|
299
347
|
);
|
|
300
348
|
|
|
301
|
-
let claim_ids: Vec<&str> =
|
|
302
|
-
|
|
349
|
+
let claim_ids: Vec<&str> = matched
|
|
350
|
+
.iter()
|
|
351
|
+
.map(|(_, record)| record.dig("claim_id").and_then(Json::as_str).unwrap())
|
|
352
|
+
.collect();
|
|
303
353
|
assert_eq!(claim_ids, vec!["c2", "nonexistent"]);
|
|
304
354
|
}
|
|
305
355
|
}
|
|
@@ -321,7 +371,15 @@ mod check_role_actor_id_tests {
|
|
|
321
371
|
store: &impl AggregateScan,
|
|
322
372
|
queries: &[QueryDef],
|
|
323
373
|
) -> Result<(), Refusal> {
|
|
324
|
-
check_role_via(
|
|
374
|
+
check_role_via(
|
|
375
|
+
command_role,
|
|
376
|
+
command_name,
|
|
377
|
+
caller_role,
|
|
378
|
+
caller_actor_id,
|
|
379
|
+
store,
|
|
380
|
+
queries,
|
|
381
|
+
Some(GOVERNANCE_ASSIGNMENTS),
|
|
382
|
+
)
|
|
325
383
|
}
|
|
326
384
|
|
|
327
385
|
/// Mirrors the merged `Store` compiled for a domain using Governance: one `RoleAssignment`
|
|
@@ -345,7 +403,11 @@ mod check_role_actor_id_tests {
|
|
|
345
403
|
QueryDef {
|
|
346
404
|
verb: "Governance::RoleAssignment.AssignmentsForActor",
|
|
347
405
|
aggregate: "Governance::RoleAssignment",
|
|
348
|
-
conditions: &[QueryCondition {
|
|
406
|
+
conditions: &[QueryCondition {
|
|
407
|
+
field: "actor_id",
|
|
408
|
+
comparator: QueryComparator::Eq,
|
|
409
|
+
value: QueryConditionValue::Arg("actor_id"),
|
|
410
|
+
}],
|
|
349
411
|
reference_hop_conditions: &[],
|
|
350
412
|
order_by: None,
|
|
351
413
|
offset: None,
|
|
@@ -356,65 +418,134 @@ mod check_role_actor_id_tests {
|
|
|
356
418
|
|
|
357
419
|
fn role_assignment(actor_id: &str, role_name: &str, ends_at: Option<&str>) -> Json {
|
|
358
420
|
Json::obj(vec![
|
|
359
|
-
(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
421
|
+
(
|
|
422
|
+
"actor_id",
|
|
423
|
+
Json::obj(vec![("value", Json::str(actor_id.to_string()))]),
|
|
424
|
+
),
|
|
425
|
+
(
|
|
426
|
+
"role_name",
|
|
427
|
+
Json::obj(vec![("value", Json::str(role_name.to_string()))]),
|
|
428
|
+
),
|
|
429
|
+
(
|
|
430
|
+
"scope",
|
|
431
|
+
Json::obj(vec![("value", Json::str("kitchen".to_string()))]),
|
|
432
|
+
),
|
|
433
|
+
(
|
|
434
|
+
"starts_at",
|
|
435
|
+
Json::obj(vec![("value", Json::str("2026-01-01".to_string()))]),
|
|
436
|
+
),
|
|
437
|
+
(
|
|
438
|
+
"ends_at",
|
|
439
|
+
ends_at.map_or(Json::Null, |ts| {
|
|
440
|
+
Json::obj(vec![("value", Json::str(ts.to_string()))])
|
|
441
|
+
}),
|
|
442
|
+
),
|
|
367
443
|
])
|
|
368
444
|
}
|
|
369
445
|
|
|
370
446
|
// An identified actor with no grant is refused even when the typed role matches.
|
|
371
447
|
#[test]
|
|
372
448
|
fn refuses_an_identified_caller_with_no_matching_grant_even_though_the_typed_role_matches() {
|
|
373
|
-
let store = FakeStore {
|
|
449
|
+
let store = FakeStore {
|
|
450
|
+
role_assignments: vec![],
|
|
451
|
+
};
|
|
374
452
|
let queries = [assignments_for_actor_query()];
|
|
375
453
|
|
|
376
|
-
let result = check_role(
|
|
454
|
+
let result = check_role(
|
|
455
|
+
Some("Chef"),
|
|
456
|
+
"Prepare",
|
|
457
|
+
Some("Chef"),
|
|
458
|
+
Some("attacker"),
|
|
459
|
+
&store,
|
|
460
|
+
&queries,
|
|
461
|
+
);
|
|
377
462
|
|
|
378
|
-
assert!(
|
|
463
|
+
assert!(
|
|
464
|
+
result.is_err(),
|
|
465
|
+
"an actor with no real grant must be refused even though it typed the right role"
|
|
466
|
+
);
|
|
379
467
|
}
|
|
380
468
|
|
|
381
469
|
// A live (non-revoked) matching grant is accepted.
|
|
382
470
|
#[test]
|
|
383
471
|
fn accepts_an_identified_caller_with_a_live_matching_grant() {
|
|
384
|
-
let store = FakeStore {
|
|
472
|
+
let store = FakeStore {
|
|
473
|
+
role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))],
|
|
474
|
+
};
|
|
385
475
|
let queries = [assignments_for_actor_query()];
|
|
386
476
|
|
|
387
|
-
let result = check_role(
|
|
477
|
+
let result = check_role(
|
|
478
|
+
Some("Chef"),
|
|
479
|
+
"Prepare",
|
|
480
|
+
Some("Chef"),
|
|
481
|
+
Some("u1"),
|
|
482
|
+
&store,
|
|
483
|
+
&queries,
|
|
484
|
+
);
|
|
388
485
|
|
|
389
|
-
assert!(
|
|
486
|
+
assert!(
|
|
487
|
+
result.is_ok(),
|
|
488
|
+
"a live matching grant must be accepted: {result:?}"
|
|
489
|
+
);
|
|
390
490
|
}
|
|
391
491
|
|
|
392
492
|
// A revoked grant (`ends_at` set) is refused.
|
|
393
493
|
#[test]
|
|
394
494
|
fn refuses_a_revoked_grant() {
|
|
395
|
-
let store = FakeStore {
|
|
495
|
+
let store = FakeStore {
|
|
496
|
+
role_assignments: vec![(
|
|
497
|
+
"ra1".to_string(),
|
|
498
|
+
role_assignment("u1", "Chef", Some("2026-06-01")),
|
|
499
|
+
)],
|
|
500
|
+
};
|
|
396
501
|
let queries = [assignments_for_actor_query()];
|
|
397
502
|
|
|
398
|
-
let result = check_role(
|
|
503
|
+
let result = check_role(
|
|
504
|
+
Some("Chef"),
|
|
505
|
+
"Prepare",
|
|
506
|
+
Some("Chef"),
|
|
507
|
+
Some("u1"),
|
|
508
|
+
&store,
|
|
509
|
+
&queries,
|
|
510
|
+
);
|
|
399
511
|
|
|
400
|
-
assert!(
|
|
512
|
+
assert!(
|
|
513
|
+
result.is_err(),
|
|
514
|
+
"a revoked grant must not authorize: {result:?}"
|
|
515
|
+
);
|
|
401
516
|
}
|
|
402
517
|
|
|
403
518
|
// An identified caller that states no role is held to what Governance assigned them.
|
|
404
519
|
#[test]
|
|
405
520
|
fn an_identified_caller_with_no_stated_role_needs_a_live_assignment_of_the_commands_role() {
|
|
406
|
-
let store = FakeStore {
|
|
521
|
+
let store = FakeStore {
|
|
522
|
+
role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))],
|
|
523
|
+
};
|
|
407
524
|
let queries = [assignments_for_actor_query()];
|
|
408
525
|
|
|
409
|
-
assert!(
|
|
410
|
-
|
|
411
|
-
|
|
526
|
+
assert!(
|
|
527
|
+
check_role(Some("Chef"), "Prepare", None, Some("u1"), &store, &queries).is_ok(),
|
|
528
|
+
"an assigned actor needs no stated role"
|
|
529
|
+
);
|
|
530
|
+
assert!(
|
|
531
|
+
check_role(Some("Waiter"), "Serve", None, Some("u1"), &store, &queries).is_err(),
|
|
532
|
+
"an actor not assigned the role is refused"
|
|
533
|
+
);
|
|
534
|
+
assert!(
|
|
535
|
+
check_role(Some("Chef"), "Prepare", None, Some("u2"), &store, &queries).is_err(),
|
|
536
|
+
"an unassigned actor is refused"
|
|
537
|
+
);
|
|
412
538
|
}
|
|
413
539
|
|
|
414
540
|
// Revoked assignments do not count for a caller with no stated role either.
|
|
415
541
|
#[test]
|
|
416
542
|
fn an_identified_caller_with_no_stated_role_is_refused_on_a_revoked_assignment() {
|
|
417
|
-
let store = FakeStore {
|
|
543
|
+
let store = FakeStore {
|
|
544
|
+
role_assignments: vec![(
|
|
545
|
+
"ra1".to_string(),
|
|
546
|
+
role_assignment("u1", "Chef", Some("2026-06-01")),
|
|
547
|
+
)],
|
|
548
|
+
};
|
|
418
549
|
let queries = [assignments_for_actor_query()];
|
|
419
550
|
|
|
420
551
|
assert!(check_role(Some("Chef"), "Prepare", None, Some("u1"), &store, &queries).is_err());
|
|
@@ -423,64 +554,149 @@ mod check_role_actor_id_tests {
|
|
|
423
554
|
// No Governance provider attached: an actor with no stated role stays unchecked, as before.
|
|
424
555
|
#[test]
|
|
425
556
|
fn an_identified_caller_with_no_stated_role_is_unchecked_when_governance_is_not_attached() {
|
|
426
|
-
let store = FakeStore {
|
|
557
|
+
let store = FakeStore {
|
|
558
|
+
role_assignments: vec![],
|
|
559
|
+
};
|
|
427
560
|
|
|
428
561
|
let result = check_role_via(Some("Chef"), "Prepare", None, Some("u1"), &store, &[], None);
|
|
429
562
|
|
|
430
|
-
assert!(
|
|
563
|
+
assert!(
|
|
564
|
+
result.is_ok(),
|
|
565
|
+
"no provider, nothing to check against: {result:?}"
|
|
566
|
+
);
|
|
431
567
|
}
|
|
432
568
|
|
|
433
569
|
// A caller with only `role:` keeps the plain string comparison, whatever grants exist.
|
|
434
570
|
#[test]
|
|
435
571
|
fn a_bare_role_only_caller_is_unaffected_by_any_real_grant_data() {
|
|
436
|
-
let store = FakeStore {
|
|
572
|
+
let store = FakeStore {
|
|
573
|
+
role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Customer", None))],
|
|
574
|
+
};
|
|
437
575
|
let queries = [assignments_for_actor_query()];
|
|
438
576
|
|
|
439
577
|
// Matching string, no actor_id: accepted.
|
|
440
|
-
let matches = check_role(
|
|
441
|
-
|
|
578
|
+
let matches = check_role(
|
|
579
|
+
Some("Chef"),
|
|
580
|
+
"Prepare",
|
|
581
|
+
Some("Chef"),
|
|
582
|
+
None,
|
|
583
|
+
&store,
|
|
584
|
+
&queries,
|
|
585
|
+
);
|
|
586
|
+
assert!(
|
|
587
|
+
matches.is_ok(),
|
|
588
|
+
"a bare matching role string must still be accepted unchanged: {matches:?}"
|
|
589
|
+
);
|
|
442
590
|
|
|
443
591
|
// Mismatched string, no actor_id: refused.
|
|
444
|
-
let mismatches = check_role(
|
|
445
|
-
|
|
592
|
+
let mismatches = check_role(
|
|
593
|
+
Some("Chef"),
|
|
594
|
+
"Prepare",
|
|
595
|
+
Some("Customer"),
|
|
596
|
+
None,
|
|
597
|
+
&store,
|
|
598
|
+
&queries,
|
|
599
|
+
);
|
|
600
|
+
assert!(
|
|
601
|
+
mismatches.is_err(),
|
|
602
|
+
"a bare mismatched role string must still refuse unchanged"
|
|
603
|
+
);
|
|
446
604
|
}
|
|
447
605
|
|
|
448
606
|
// Without an `AssignmentsForActor` query, a bound `actor_id` falls back to the string check.
|
|
449
607
|
#[test]
|
|
450
608
|
fn falls_back_to_the_string_check_when_this_domain_never_attached_governance() {
|
|
451
|
-
let store = FakeStore {
|
|
609
|
+
let store = FakeStore {
|
|
610
|
+
role_assignments: vec![],
|
|
611
|
+
};
|
|
452
612
|
let queries: [QueryDef; 0] = [];
|
|
453
613
|
|
|
454
|
-
let matches = check_role(
|
|
614
|
+
let matches = check_role(
|
|
615
|
+
Some("Chef"),
|
|
616
|
+
"Prepare",
|
|
617
|
+
Some("Chef"),
|
|
618
|
+
Some("whoever"),
|
|
619
|
+
&store,
|
|
620
|
+
&queries,
|
|
621
|
+
);
|
|
455
622
|
assert!(matches.is_ok(), "no AssignmentsForActor query compiled in -> string fallback, matching role -> accepted: {matches:?}");
|
|
456
623
|
|
|
457
|
-
let mismatches = check_role(
|
|
624
|
+
let mismatches = check_role(
|
|
625
|
+
Some("Chef"),
|
|
626
|
+
"Prepare",
|
|
627
|
+
Some("Customer"),
|
|
628
|
+
Some("whoever"),
|
|
629
|
+
&store,
|
|
630
|
+
&queries,
|
|
631
|
+
);
|
|
458
632
|
assert!(mismatches.is_err(), "no AssignmentsForActor query compiled in -> string fallback, mismatched role -> refused");
|
|
459
633
|
}
|
|
460
634
|
|
|
461
635
|
// The declared provider verb is honoured, not the Governance query name...
|
|
462
636
|
#[test]
|
|
463
637
|
fn check_role_via_reads_whichever_assignments_query_the_chapter_declared() {
|
|
464
|
-
let declared = QueryDef {
|
|
638
|
+
let declared = QueryDef {
|
|
639
|
+
verb: "Access::Grant.HeldBy",
|
|
640
|
+
..assignments_for_actor_query()
|
|
641
|
+
};
|
|
465
642
|
let queries = [declared];
|
|
466
643
|
|
|
467
|
-
let granted = FakeStore {
|
|
468
|
-
|
|
469
|
-
|
|
644
|
+
let granted = FakeStore {
|
|
645
|
+
role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))],
|
|
646
|
+
};
|
|
647
|
+
let accepted = check_role_via(
|
|
648
|
+
Some("Chef"),
|
|
649
|
+
"Prepare",
|
|
650
|
+
Some("Chef"),
|
|
651
|
+
Some("u1"),
|
|
652
|
+
&granted,
|
|
653
|
+
&queries,
|
|
654
|
+
Some("Access::Grant.HeldBy"),
|
|
655
|
+
);
|
|
656
|
+
assert!(
|
|
657
|
+
accepted.is_ok(),
|
|
658
|
+
"a live grant under the declared query must be accepted: {accepted:?}"
|
|
659
|
+
);
|
|
470
660
|
|
|
471
|
-
let ungranted = FakeStore {
|
|
472
|
-
|
|
473
|
-
|
|
661
|
+
let ungranted = FakeStore {
|
|
662
|
+
role_assignments: vec![],
|
|
663
|
+
};
|
|
664
|
+
let refused = check_role_via(
|
|
665
|
+
Some("Chef"),
|
|
666
|
+
"Prepare",
|
|
667
|
+
Some("Chef"),
|
|
668
|
+
Some("u1"),
|
|
669
|
+
&ungranted,
|
|
670
|
+
&queries,
|
|
671
|
+
Some("Access::Grant.HeldBy"),
|
|
672
|
+
);
|
|
673
|
+
assert!(
|
|
674
|
+
refused.is_err(),
|
|
675
|
+
"no grant under the declared query must refuse even though the typed role matches"
|
|
676
|
+
);
|
|
474
677
|
}
|
|
475
678
|
|
|
476
679
|
// ...and with no declared provider, a Governance-named query is ignored.
|
|
477
680
|
#[test]
|
|
478
681
|
fn check_role_via_ignores_a_governance_named_query_nobody_declared() {
|
|
479
|
-
let store = FakeStore {
|
|
682
|
+
let store = FakeStore {
|
|
683
|
+
role_assignments: vec![],
|
|
684
|
+
};
|
|
480
685
|
let queries = [assignments_for_actor_query()];
|
|
481
686
|
|
|
482
|
-
let result = check_role_via(
|
|
483
|
-
|
|
687
|
+
let result = check_role_via(
|
|
688
|
+
Some("Chef"),
|
|
689
|
+
"Prepare",
|
|
690
|
+
Some("Chef"),
|
|
691
|
+
Some("whoever"),
|
|
692
|
+
&store,
|
|
693
|
+
&queries,
|
|
694
|
+
None,
|
|
695
|
+
);
|
|
696
|
+
assert!(
|
|
697
|
+
result.is_ok(),
|
|
698
|
+
"no declared provider -> string fallback, matching role -> accepted: {result:?}"
|
|
699
|
+
);
|
|
484
700
|
}
|
|
485
701
|
}
|
|
486
702
|
|
|
@@ -489,6 +705,8 @@ mod json_cache_tests {
|
|
|
489
705
|
use super::{InMemoryRepository, Repository};
|
|
490
706
|
use crate::kernel::Json;
|
|
491
707
|
|
|
708
|
+
// reason: `json_entries` takes `fn(&T) -> Json`, so the signature is fixed by the caller.
|
|
709
|
+
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
492
710
|
fn render(n: &i64) -> Json {
|
|
493
711
|
Json::obj(vec![("n", Json::int(*n))])
|
|
494
712
|
}
|
|
@@ -499,11 +717,20 @@ mod json_cache_tests {
|
|
|
499
717
|
let mut repo: InMemoryRepository<i64> = InMemoryRepository::new();
|
|
500
718
|
repo.save("b", 2);
|
|
501
719
|
repo.save("a", 1);
|
|
502
|
-
let first: Vec<(String, Json)> = repo
|
|
503
|
-
|
|
720
|
+
let first: Vec<(String, Json)> = repo
|
|
721
|
+
.json_entries(render)
|
|
722
|
+
.map(|(id, json)| (id.clone(), json.clone()))
|
|
723
|
+
.collect();
|
|
724
|
+
assert_eq!(
|
|
725
|
+
first,
|
|
726
|
+
vec![("a".to_string(), render(&1)), ("b".to_string(), render(&2))]
|
|
727
|
+
);
|
|
504
728
|
|
|
505
729
|
repo.save("a", 10);
|
|
506
|
-
let second: Vec<Json> = repo
|
|
730
|
+
let second: Vec<Json> = repo
|
|
731
|
+
.json_entries(render)
|
|
732
|
+
.map(|(_, json)| json.clone())
|
|
733
|
+
.collect();
|
|
507
734
|
assert_eq!(second, vec![render(&10), render(&2)]);
|
|
508
735
|
}
|
|
509
736
|
}
|