@feltdb/core 0.13.0 → 0.16.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.
- package/dist/agent-runtime.d.ts +64 -2
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +121 -11
- package/dist/agent-state.d.ts +68 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +210 -12
- package/dist/agent.d.ts +10 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -1
- package/dist/build-identity.json +12 -4
- package/dist/cli/commands.js +214 -10
- package/dist/cli/publish-engine.js +116 -16
- package/dist/collection.d.ts +3 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +33 -8
- package/dist/create/build-identity.json +12 -4
- package/dist/create/cli.js +1 -1
- package/dist/create/create.js +4 -6
- package/dist/create/managed-account.js +93 -35
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +8 -511
- package/dist/create/server-source/Cargo.toml +2 -13
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
- package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
- package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +1134 -96
- package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
- package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
- package/dist/db.d.ts +75 -10
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +168 -29
- package/dist/deployment.d.ts +19 -0
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +38 -2
- package/dist/embedded-query.d.ts.map +1 -1
- package/dist/embedded-query.js +5 -4
- package/dist/embedded-transaction.d.ts +8 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +55 -19
- package/dist/error-codes.d.ts +36 -2
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +50 -3
- package/dist/feltdb.d.ts +7 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +27 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +145 -7
- package/dist/http-client.d.ts +11 -3
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +27 -14
- package/dist/http-db.d.ts +43 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +287 -59
- package/dist/index.js +1 -1
- package/dist/local-state-lock.d.ts +34 -0
- package/dist/local-state-lock.d.ts.map +1 -0
- package/dist/local-state-lock.js +225 -0
- package/dist/managed-recovery.d.ts +6 -0
- package/dist/managed-recovery.d.ts.map +1 -1
- package/dist/managed-recovery.js +9 -0
- package/dist/reactive-graph.d.ts +0 -3
- package/dist/reactive-graph.d.ts.map +1 -1
- package/dist/reactive-graph.js +11 -1
- package/dist/runtime-registry.d.ts +7 -2
- package/dist/runtime-registry.d.ts.map +1 -1
- package/dist/runtime-registry.js +2 -2
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-DtybUY0q.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
- package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +27 -11
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +20 -14
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workload.d.ts +86 -6
- package/dist/workload.d.ts.map +1 -1
- package/dist/workload.js +51 -3
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -3
- package/package.json +1 -1
- package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
- package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
- package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
- package/dist/create/server-source/crates/feltdb/README.md +0 -75
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
- package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
- package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
- package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
- package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
- package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
- package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
- package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7274
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
- package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
- package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
- package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
- package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
- package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
- package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -5894
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
- package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
- package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
- package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
- package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1133
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
- package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -2886
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
- package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
- package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
- package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
- package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
- package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
- package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
- package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
- package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
- package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
- package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
- package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
- package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
- package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
- package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
- package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
- package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
- package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
- package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
- package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
- package/dist/studio-app/assets/index-Dql1hS5m.js +0 -30
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
//! Credential redaction for everything the server writes outward: log lines,
|
|
2
|
+
//! error responses and diagnostics.
|
|
3
|
+
//!
|
|
4
|
+
//! Redaction is by shape, not by knowing the secret: FeltDB service keys
|
|
5
|
+
//! (`fdb_live_…`), end-user session tokens (`session_token_…`), `Bearer`
|
|
6
|
+
//! credentials, credential-named query parameters (`token=`, `access_token=`,
|
|
7
|
+
//! `api_key=`, `secret=`, `password=`, …), signed console session cookies
|
|
8
|
+
//! (`feltdb_session=`), and the password of a `scheme://user:password@host`
|
|
9
|
+
//! connection string. Each is replaced by `[REDACTED]`, keeping the prefix so
|
|
10
|
+
//! an operator can still tell what kind of credential was there.
|
|
11
|
+
|
|
12
|
+
use std::{borrow::Cow, io};
|
|
13
|
+
|
|
14
|
+
pub const REDACTED: &str = "[REDACTED]";
|
|
15
|
+
|
|
16
|
+
const PREFIXED: &[&str] = &["fdb_live_", "session_token_"];
|
|
17
|
+
const ASSIGNED: &[&str] = &[
|
|
18
|
+
"access_token=",
|
|
19
|
+
"token=",
|
|
20
|
+
"api_key=",
|
|
21
|
+
"apikey=",
|
|
22
|
+
"api-key=",
|
|
23
|
+
"secret=",
|
|
24
|
+
"password=",
|
|
25
|
+
"passwd=",
|
|
26
|
+
"feltdb_session=",
|
|
27
|
+
"x-feltdb-intent-service=",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
fn credential_char(byte: u8) -> bool {
|
|
31
|
+
byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~' | b'+' | b'/' | b'=' | b'%')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Whether `text` may hold something [`redact`] would change. Cheap; lets hot
|
|
35
|
+
/// paths skip the copy.
|
|
36
|
+
pub fn may_contain_credential(text: &str) -> bool {
|
|
37
|
+
let lower = text.to_ascii_lowercase();
|
|
38
|
+
PREFIXED.iter().any(|prefix| lower.contains(prefix))
|
|
39
|
+
|| ASSIGNED.iter().any(|name| lower.contains(name))
|
|
40
|
+
|| lower.contains("bearer ")
|
|
41
|
+
|| lower.contains("://")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Replace every credential-shaped span in `text` with `[REDACTED]`.
|
|
45
|
+
pub fn redact(text: &str) -> Cow<'_, str> {
|
|
46
|
+
if !may_contain_credential(text) {
|
|
47
|
+
return Cow::Borrowed(text);
|
|
48
|
+
}
|
|
49
|
+
let bytes = text.as_bytes();
|
|
50
|
+
let lower = text.to_ascii_lowercase();
|
|
51
|
+
let lower = lower.as_bytes();
|
|
52
|
+
let mut out = String::with_capacity(text.len());
|
|
53
|
+
let mut index = 0;
|
|
54
|
+
let mut changed = false;
|
|
55
|
+
'scan: while index < bytes.len() {
|
|
56
|
+
// `fdb_live_…`, `session_token_…`: keep the prefix, drop the material.
|
|
57
|
+
for prefix in PREFIXED {
|
|
58
|
+
if lower[index..].starts_with(prefix.as_bytes()) {
|
|
59
|
+
let start = index + prefix.len();
|
|
60
|
+
let mut end = start;
|
|
61
|
+
while end < bytes.len() && bytes[end].is_ascii_alphanumeric() {
|
|
62
|
+
end += 1;
|
|
63
|
+
}
|
|
64
|
+
if end > start {
|
|
65
|
+
out.push_str(&text[index..start]);
|
|
66
|
+
out.push_str(REDACTED);
|
|
67
|
+
index = end;
|
|
68
|
+
changed = true;
|
|
69
|
+
continue 'scan;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// `Bearer <credential>`
|
|
74
|
+
if lower[index..].starts_with(b"bearer ") && (index == 0 || !bytes[index - 1].is_ascii_alphanumeric()) {
|
|
75
|
+
let start = index + "bearer ".len();
|
|
76
|
+
let mut end = start;
|
|
77
|
+
while end < bytes.len() && credential_char(bytes[end]) {
|
|
78
|
+
end += 1;
|
|
79
|
+
}
|
|
80
|
+
// Prose ("valid bearer API key") is not a credential; issued
|
|
81
|
+
// tokens are far longer than any English word.
|
|
82
|
+
if end - start >= 12 && !text[start..end].starts_with(REDACTED) {
|
|
83
|
+
out.push_str(&text[index..start]);
|
|
84
|
+
out.push_str(REDACTED);
|
|
85
|
+
index = end;
|
|
86
|
+
changed = true;
|
|
87
|
+
continue 'scan;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// `token=<value>` and friends, at a word boundary.
|
|
91
|
+
if index == 0 || !bytes[index - 1].is_ascii_alphanumeric() {
|
|
92
|
+
for name in ASSIGNED {
|
|
93
|
+
if lower[index..].starts_with(name.as_bytes()) {
|
|
94
|
+
let start = index + name.len();
|
|
95
|
+
let mut end = start;
|
|
96
|
+
while end < bytes.len() && credential_char(bytes[end]) && bytes[end] != b'&' {
|
|
97
|
+
end += 1;
|
|
98
|
+
}
|
|
99
|
+
if end > start {
|
|
100
|
+
out.push_str(&text[index..start]);
|
|
101
|
+
out.push_str(REDACTED);
|
|
102
|
+
index = end;
|
|
103
|
+
changed = true;
|
|
104
|
+
continue 'scan;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// `scheme://user:password@host` — redact the password.
|
|
110
|
+
if lower[index..].starts_with(b"://") {
|
|
111
|
+
let authority_start = index + 3;
|
|
112
|
+
let mut end = authority_start;
|
|
113
|
+
while end < bytes.len() && !matches!(bytes[end], b'/' | b'@' | b' ' | b'"' | b'\'' | b'?' | b'#' | b'\n') {
|
|
114
|
+
end += 1;
|
|
115
|
+
}
|
|
116
|
+
if end < bytes.len() && bytes[end] == b'@' {
|
|
117
|
+
if let Some(colon) = text[authority_start..end].find(':') {
|
|
118
|
+
let password_start = authority_start + colon + 1;
|
|
119
|
+
if password_start < end {
|
|
120
|
+
out.push_str(&text[index..password_start]);
|
|
121
|
+
out.push_str(REDACTED);
|
|
122
|
+
index = end;
|
|
123
|
+
changed = true;
|
|
124
|
+
continue 'scan;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let next = text[index..].chars().next().map_or(1, char::len_utf8);
|
|
130
|
+
out.push_str(&text[index..index + next]);
|
|
131
|
+
index += next;
|
|
132
|
+
}
|
|
133
|
+
if changed {
|
|
134
|
+
Cow::Owned(out)
|
|
135
|
+
} else {
|
|
136
|
+
Cow::Borrowed(text)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// A `tracing_subscriber` writer that redacts each formatted event before it
|
|
141
|
+
/// reaches stdout. The fmt layer writes one event per `write` call, so a
|
|
142
|
+
/// credential never straddles two calls.
|
|
143
|
+
#[derive(Clone, Copy, Default)]
|
|
144
|
+
pub struct RedactingStdout;
|
|
145
|
+
|
|
146
|
+
pub struct RedactingWriter(io::Stdout);
|
|
147
|
+
|
|
148
|
+
impl io::Write for RedactingWriter {
|
|
149
|
+
fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
|
|
150
|
+
match std::str::from_utf8(buffer) {
|
|
151
|
+
Ok(text) => {
|
|
152
|
+
self.0.write_all(redact(text).as_bytes())?;
|
|
153
|
+
}
|
|
154
|
+
Err(_) => self.0.write_all(buffer)?,
|
|
155
|
+
}
|
|
156
|
+
Ok(buffer.len())
|
|
157
|
+
}
|
|
158
|
+
fn flush(&mut self) -> io::Result<()> {
|
|
159
|
+
self.0.flush()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for RedactingStdout {
|
|
164
|
+
type Writer = RedactingWriter;
|
|
165
|
+
fn make_writer(&'a self) -> Self::Writer {
|
|
166
|
+
RedactingWriter(io::stdout())
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[cfg(test)]
|
|
171
|
+
mod tests {
|
|
172
|
+
use super::*;
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn redacts_every_credential_shape() {
|
|
176
|
+
let key = ["fdb", "live", "Q7rT2mXk9LpA4sVb8NcY1wEz6HdJ3uFg5KoR0iPq"].join("_");
|
|
177
|
+
let cases = [
|
|
178
|
+
(format!("invalid type: string \"{key}\", expected a sequence"), "fdb_live_[REDACTED]"),
|
|
179
|
+
(format!("authorization: Bearer {key}"), "Bearer [REDACTED]"),
|
|
180
|
+
("authorization: bearer session_token_abc123XYZ".to_string(), "bearer [REDACTED]"),
|
|
181
|
+
("GET /v1/events?access_token=abc.def-ghi&cursor=fh1-00".to_string(), "access_token=[REDACTED]&cursor=fh1-00"),
|
|
182
|
+
("uri=/x?token=s3cr3t".to_string(), "token=[REDACTED]"),
|
|
183
|
+
("client_secret=s3cr3t".to_string(), "client_secret=[REDACTED]"),
|
|
184
|
+
("cookie: feltdb_session=eyJ2.c2ln".to_string(), "feltdb_session=[REDACTED]"),
|
|
185
|
+
("postgres://app:hunter2@db.internal/feltdb".to_string(), "postgres://app:[REDACTED]@db.internal/feltdb"),
|
|
186
|
+
];
|
|
187
|
+
for (input, expected) in cases {
|
|
188
|
+
let output = redact(&input);
|
|
189
|
+
assert!(output.contains(expected), "{input} -> {output}");
|
|
190
|
+
assert!(!output.contains("hunter2") && !output.contains("s3cr3t") && !output.contains(&key[9..]));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn leaves_ordinary_text_alone() {
|
|
196
|
+
for text in [
|
|
197
|
+
"missing scope keys:admin",
|
|
198
|
+
"valid bearer API key required",
|
|
199
|
+
"https://api.feltdb.com/v1/status",
|
|
200
|
+
"tokens: 3, key_id=abc",
|
|
201
|
+
"the token budget",
|
|
202
|
+
"fdb_live_ prefix",
|
|
203
|
+
] {
|
|
204
|
+
assert_eq!(redact(text), text);
|
|
205
|
+
}
|
|
206
|
+
assert!(matches!(redact("plain"), Cow::Borrowed(_)));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -10,7 +10,6 @@ use argon2::{
|
|
|
10
10
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
|
11
11
|
Argon2,
|
|
12
12
|
};
|
|
13
|
-
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
|
14
13
|
use rand::{distributions::Alphanumeric, Rng};
|
|
15
14
|
use serde::{Deserialize, Serialize};
|
|
16
15
|
|
|
@@ -192,23 +191,24 @@ fn slug(value: &str) -> String {
|
|
|
192
191
|
.collect::<Vec<_>>()
|
|
193
192
|
.join("-")
|
|
194
193
|
}
|
|
195
|
-
|
|
194
|
+
/// Whether `user_id` is a platform owner: the platform user registered under
|
|
195
|
+
/// that id has an identity listed in `FELTDB_PLATFORM_OWNERS`. Before 0.15.0
|
|
196
|
+
/// this compared `user_id` with an id derived from a prefix of each owner's
|
|
197
|
+
/// identity, so any identity sharing that prefix was an owner too.
|
|
198
|
+
fn platform_owner_user(records: &PlatformRecords, user_id: &str) -> bool {
|
|
199
|
+
let Some(identity) = records
|
|
200
|
+
.users
|
|
201
|
+
.iter()
|
|
202
|
+
.find(|user| user.user_id == user_id)
|
|
203
|
+
.map(|user| user.identity.as_str())
|
|
204
|
+
else {
|
|
205
|
+
return false;
|
|
206
|
+
};
|
|
196
207
|
std::env::var("FELTDB_PLATFORM_OWNERS")
|
|
197
208
|
.unwrap_or_default()
|
|
198
209
|
.split(',')
|
|
199
210
|
.map(|owner| owner.trim().to_lowercase())
|
|
200
|
-
.
|
|
201
|
-
.map(|owner| {
|
|
202
|
-
format!(
|
|
203
|
-
"user_{}",
|
|
204
|
-
URL_SAFE_NO_PAD
|
|
205
|
-
.encode(owner)
|
|
206
|
-
.chars()
|
|
207
|
-
.take(18)
|
|
208
|
-
.collect::<String>()
|
|
209
|
-
)
|
|
210
|
-
})
|
|
211
|
-
.any(|owner_id| owner_id == user_id)
|
|
211
|
+
.any(|owner| !owner.is_empty() && owner == identity)
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
pub fn tenant_role_grants() -> Vec<RoleGrant> {
|
|
@@ -424,6 +424,100 @@ impl TenancyStore {
|
|
|
424
424
|
);
|
|
425
425
|
self.persist(&records)
|
|
426
426
|
}
|
|
427
|
+
/// The application an active service key is bound to, if any.
|
|
428
|
+
pub fn service_binding(&self, service_id: &str) -> Option<ApplicationServicePrincipal> {
|
|
429
|
+
self.records
|
|
430
|
+
.read()
|
|
431
|
+
.ok()?
|
|
432
|
+
.application_service_principals
|
|
433
|
+
.iter()
|
|
434
|
+
.find(|principal| {
|
|
435
|
+
principal.service_id == service_id && principal.status == MembershipStatus::Active
|
|
436
|
+
})
|
|
437
|
+
.cloned()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/// The application record, without an authorization decision. Callers
|
|
441
|
+
/// must have authorized the caller for this application already.
|
|
442
|
+
pub fn application_record(&self, application_id: &str) -> Option<Application> {
|
|
443
|
+
self.records
|
|
444
|
+
.read()
|
|
445
|
+
.ok()?
|
|
446
|
+
.applications
|
|
447
|
+
.iter()
|
|
448
|
+
.find(|app| app.id == application_id)
|
|
449
|
+
.cloned()
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/// Ids of the active service keys bound to one application.
|
|
453
|
+
pub fn application_service_ids(&self, application_id: &str) -> Vec<String> {
|
|
454
|
+
self.records
|
|
455
|
+
.read()
|
|
456
|
+
.map(|records| {
|
|
457
|
+
records
|
|
458
|
+
.application_service_principals
|
|
459
|
+
.iter()
|
|
460
|
+
.filter(|principal| {
|
|
461
|
+
principal.application_id == application_id
|
|
462
|
+
&& principal.status == MembershipStatus::Active
|
|
463
|
+
})
|
|
464
|
+
.map(|principal| principal.service_id.clone())
|
|
465
|
+
.collect()
|
|
466
|
+
})
|
|
467
|
+
.unwrap_or_default()
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/// Move an application binding from a rotated-out key to its replacement,
|
|
471
|
+
/// or end it when the key is revoked (`replacement` of `None`).
|
|
472
|
+
///
|
|
473
|
+
/// The caller has already authorized the actor for the bound application;
|
|
474
|
+
/// this records the change and its audit event.
|
|
475
|
+
pub fn transfer_application_service(
|
|
476
|
+
&self,
|
|
477
|
+
actor: &str,
|
|
478
|
+
service_id: &str,
|
|
479
|
+
replacement: Option<&str>,
|
|
480
|
+
) -> Result<(), String> {
|
|
481
|
+
let mut records = self
|
|
482
|
+
.records
|
|
483
|
+
.write()
|
|
484
|
+
.map_err(|_| "tenancy store lock poisoned")?;
|
|
485
|
+
let Some(index) = records
|
|
486
|
+
.application_service_principals
|
|
487
|
+
.iter()
|
|
488
|
+
.position(|principal| {
|
|
489
|
+
principal.service_id == service_id && principal.status == MembershipStatus::Active
|
|
490
|
+
})
|
|
491
|
+
else {
|
|
492
|
+
return Ok(());
|
|
493
|
+
};
|
|
494
|
+
records.application_service_principals[index].status = MembershipStatus::Revoked;
|
|
495
|
+
let bound = records.application_service_principals[index].clone();
|
|
496
|
+
if let Some(replacement) = replacement {
|
|
497
|
+
records
|
|
498
|
+
.application_service_principals
|
|
499
|
+
.push(ApplicationServicePrincipal {
|
|
500
|
+
service_id: replacement.into(),
|
|
501
|
+
created_by: actor.into(),
|
|
502
|
+
created_at: now(),
|
|
503
|
+
status: MembershipStatus::Active,
|
|
504
|
+
..bound.clone()
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
Self::audit(
|
|
508
|
+
&mut records,
|
|
509
|
+
if replacement.is_some() {
|
|
510
|
+
"application.service.rotated"
|
|
511
|
+
} else {
|
|
512
|
+
"application.service.revoked"
|
|
513
|
+
},
|
|
514
|
+
actor,
|
|
515
|
+
&bound.tenant_id,
|
|
516
|
+
Some(&bound.application_id),
|
|
517
|
+
service_id,
|
|
518
|
+
);
|
|
519
|
+
self.persist(&records)
|
|
520
|
+
}
|
|
427
521
|
fn persist(&self, records: &PlatformRecords) -> Result<(), String> {
|
|
428
522
|
if let Some(parent) = self.path.parent() {
|
|
429
523
|
fs::create_dir_all(parent).map_err(|error| error.to_string())?;
|
|
@@ -506,18 +600,60 @@ impl TenancyStore {
|
|
|
506
600
|
.records
|
|
507
601
|
.write()
|
|
508
602
|
.map_err(|_| "tenancy store lock poisoned")?;
|
|
509
|
-
|
|
510
|
-
|
|
603
|
+
let identity_normalized = identity.trim().to_lowercase();
|
|
604
|
+
if let Some(existing) = records.users.iter().find(|user| user.user_id == user_id) {
|
|
605
|
+
// One user id is one identity. Before 0.15.0 a second identity
|
|
606
|
+
// that derived the same id silently shared the first one's
|
|
607
|
+
// tenants; refuse instead.
|
|
608
|
+
if existing.identity == identity_normalized {
|
|
609
|
+
return Ok(());
|
|
610
|
+
}
|
|
611
|
+
return Err(format!(
|
|
612
|
+
"platform user {user_id} is registered to a different identity"
|
|
613
|
+
));
|
|
511
614
|
}
|
|
512
615
|
records.users.push(PlatformUser {
|
|
513
616
|
user_id: user_id.into(),
|
|
514
|
-
identity:
|
|
617
|
+
identity: identity_normalized,
|
|
515
618
|
status: MembershipStatus::Active,
|
|
516
619
|
created_at: now(),
|
|
517
620
|
});
|
|
518
621
|
self.persist(&records)
|
|
519
622
|
}
|
|
520
623
|
|
|
624
|
+
pub fn platform_user_exists(&self, user_id: &str) -> bool {
|
|
625
|
+
self.records
|
|
626
|
+
.read()
|
|
627
|
+
.is_ok_and(|records| records.users.iter().any(|user| user.user_id == user_id))
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/// The identity a platform user was registered with.
|
|
631
|
+
pub fn platform_user_identity(&self, user_id: &str) -> Option<String> {
|
|
632
|
+
self.records.read().ok().and_then(|records| {
|
|
633
|
+
records
|
|
634
|
+
.users
|
|
635
|
+
.iter()
|
|
636
|
+
.find(|user| user.user_id == user_id)
|
|
637
|
+
.map(|user| user.identity.clone())
|
|
638
|
+
})
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/// User ids of platform users registered with `identity` (normalized).
|
|
642
|
+
pub fn platform_users_with_identity(&self, identity: &str) -> Vec<String> {
|
|
643
|
+
let identity = identity.trim().to_lowercase();
|
|
644
|
+
self.records
|
|
645
|
+
.read()
|
|
646
|
+
.map(|records| {
|
|
647
|
+
records
|
|
648
|
+
.users
|
|
649
|
+
.iter()
|
|
650
|
+
.filter(|user| user.identity == identity)
|
|
651
|
+
.map(|user| user.user_id.clone())
|
|
652
|
+
.collect()
|
|
653
|
+
})
|
|
654
|
+
.unwrap_or_default()
|
|
655
|
+
}
|
|
656
|
+
|
|
521
657
|
pub fn platform_update_user(
|
|
522
658
|
&self,
|
|
523
659
|
actor: &str,
|
|
@@ -1037,7 +1173,9 @@ impl TenancyStore {
|
|
|
1037
1173
|
if !self.platform_user_active(user_id) {
|
|
1038
1174
|
return false;
|
|
1039
1175
|
}
|
|
1040
|
-
if
|
|
1176
|
+
if self.records.read().is_ok_and(|records| {
|
|
1177
|
+
platform_owner_user(&records, user_id)
|
|
1178
|
+
}) {
|
|
1041
1179
|
return self
|
|
1042
1180
|
.records
|
|
1043
1181
|
.read()
|
|
@@ -1112,7 +1250,7 @@ impl TenancyStore {
|
|
|
1112
1250
|
{
|
|
1113
1251
|
return false;
|
|
1114
1252
|
}
|
|
1115
|
-
if platform_owner_user(user_id) {
|
|
1253
|
+
if platform_owner_user(&records, user_id) {
|
|
1116
1254
|
return true;
|
|
1117
1255
|
}
|
|
1118
1256
|
let memberships: Vec<Membership> = records
|
|
@@ -1311,6 +1449,47 @@ mod tests {
|
|
|
1311
1449
|
TenancyStore::load(path).unwrap()
|
|
1312
1450
|
}
|
|
1313
1451
|
|
|
1452
|
+
#[test]
|
|
1453
|
+
fn a_user_id_is_never_shared_by_two_identities() {
|
|
1454
|
+
let store = store("platform-user-identity");
|
|
1455
|
+
store
|
|
1456
|
+
.register_platform_user("user_shared", "first@example.com")
|
|
1457
|
+
.unwrap();
|
|
1458
|
+
// Same identity (normalized) is idempotent.
|
|
1459
|
+
store
|
|
1460
|
+
.register_platform_user("user_shared", " First@Example.com ")
|
|
1461
|
+
.unwrap();
|
|
1462
|
+
let refused = store
|
|
1463
|
+
.register_platform_user("user_shared", "second@example.com")
|
|
1464
|
+
.unwrap_err();
|
|
1465
|
+
assert!(refused.contains("different identity"), "{refused}");
|
|
1466
|
+
assert_eq!(
|
|
1467
|
+
store.platform_user_identity("user_shared").as_deref(),
|
|
1468
|
+
Some("first@example.com")
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
#[test]
|
|
1473
|
+
fn platform_ownership_follows_the_registered_identity_not_an_id_prefix() {
|
|
1474
|
+
let store = store("platform-owner-identity");
|
|
1475
|
+
let owner_id = crate::console_accounts::legacy_user_id("owner-account@example.com");
|
|
1476
|
+
// An identity that shares the owner's 13-character prefix used to derive
|
|
1477
|
+
// the owner's id; it is registered first under that id here.
|
|
1478
|
+
store
|
|
1479
|
+
.register_platform_user(&owner_id, "owner-account@evil.example")
|
|
1480
|
+
.unwrap();
|
|
1481
|
+
let tenant = store.create_tenant("user_someone_else", "Victim").unwrap();
|
|
1482
|
+
std::env::set_var("FELTDB_PLATFORM_OWNERS", "owner-account@example.com");
|
|
1483
|
+
let colliding = store.authorize_tenant(&owner_id, &tenant.id, "applications:read");
|
|
1484
|
+
store
|
|
1485
|
+
.register_platform_user("user_real_owner", "owner-account@example.com")
|
|
1486
|
+
.unwrap();
|
|
1487
|
+
let genuine = store.authorize_tenant("user_real_owner", &tenant.id, "applications:read");
|
|
1488
|
+
std::env::remove_var("FELTDB_PLATFORM_OWNERS");
|
|
1489
|
+
assert!(!colliding, "an id-prefix collision must not confer platform ownership");
|
|
1490
|
+
assert!(genuine, "the owner's registered identity confers platform ownership");
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1314
1493
|
#[test]
|
|
1315
1494
|
fn tenant_and_application_are_isolated() {
|
|
1316
1495
|
let store = store("isolation");
|