@feltdb/core 0.8.4 → 0.8.5
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/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +165 -0
- package/dist/create/server-source/Cargo.toml +9 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
- package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
- package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
- package/dist/create/server-source/crates/feltdb/src/lib.rs +507 -6
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
- package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +326 -6
- package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-DaNwCLRX.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
- package/dist/studio-app/assets/{index-DwgNAIIX.js → index-j8IlhNqJ.js} +1 -1
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workload.d.ts +2 -1
- package/dist/workload.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
|
@@ -166,7 +166,8 @@ fn a_scan_result_survives_concurrent_mutation() {
|
|
|
166
166
|
// The scan returns owned records, so mutation after the lock is released
|
|
167
167
|
// cannot invalidate or rewrite what the caller already holds.
|
|
168
168
|
for index in 0..200 {
|
|
169
|
-
db.delete(&format!("items:record-{index:05}"))
|
|
169
|
+
db.delete(&format!("items:record-{index:05}"))
|
|
170
|
+
.expect("delete");
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
assert_eq!(matches.len(), 50);
|
|
@@ -41,7 +41,8 @@ fn database() -> (TempDir, FeltDb) {
|
|
|
41
41
|
/// A database with `status` and `tenantId` indexed on `items`.
|
|
42
42
|
fn indexed() -> (TempDir, FeltDb) {
|
|
43
43
|
let (directory, db) = database();
|
|
44
|
-
db.create_equality_index("items", "status")
|
|
44
|
+
db.create_equality_index("items", "status")
|
|
45
|
+
.expect("declare");
|
|
45
46
|
db.create_equality_index("items", "tenantId")
|
|
46
47
|
.expect("declare");
|
|
47
48
|
(directory, db)
|
|
@@ -53,7 +54,9 @@ fn indexed() -> (TempDir, FeltDb) {
|
|
|
53
54
|
fn assert_index_matches_state(db: &FeltDb, after: &str) {
|
|
54
55
|
match db.verify_equality_index().expect("verify") {
|
|
55
56
|
Ok(()) => {}
|
|
56
|
-
Err(divergence) =>
|
|
57
|
+
Err(divergence) => {
|
|
58
|
+
panic!("index diverged from authoritative state after {after}: {divergence}")
|
|
59
|
+
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -67,7 +70,11 @@ fn ids(db: &FeltDb, field: &str, value: Value) -> Vec<String> {
|
|
|
67
70
|
candidates(db, field, value)
|
|
68
71
|
.expect("the field is indexed and the value is indexable")
|
|
69
72
|
.into_iter()
|
|
70
|
-
.map(|key|
|
|
73
|
+
.map(|key| {
|
|
74
|
+
key.split_once(':')
|
|
75
|
+
.map(|(_, id)| id.to_string())
|
|
76
|
+
.unwrap_or(key)
|
|
77
|
+
})
|
|
71
78
|
.collect()
|
|
72
79
|
}
|
|
73
80
|
|
|
@@ -155,7 +162,11 @@ fn an_update_that_leaves_the_indexed_field_alone_leaves_the_index_alone() {
|
|
|
155
162
|
db.update("items:a", &json!({ "status": "active", "note": "after" }))
|
|
156
163
|
.expect("update");
|
|
157
164
|
|
|
158
|
-
assert_eq!(
|
|
165
|
+
assert_eq!(
|
|
166
|
+
snapshot(&db),
|
|
167
|
+
before,
|
|
168
|
+
"an unrelated field change is invisible to the index"
|
|
169
|
+
);
|
|
159
170
|
assert_index_matches_state(&db, "an unrelated field changing");
|
|
160
171
|
}
|
|
161
172
|
|
|
@@ -260,7 +271,11 @@ fn unindexable_values_are_excluded_rather_than_coerced() {
|
|
|
260
271
|
put(&db, "object", json!({ "status": { "state": "active" } }));
|
|
261
272
|
put(&db, "array", json!({ "status": ["active"] }));
|
|
262
273
|
put(&db, "scalar", json!({ "status": "active" }));
|
|
263
|
-
put(
|
|
274
|
+
put(
|
|
275
|
+
&db,
|
|
276
|
+
"not-an-object",
|
|
277
|
+
json!("items are usually objects, but need not be"),
|
|
278
|
+
);
|
|
264
279
|
|
|
265
280
|
assert_eq!(
|
|
266
281
|
ids(&db, "status", json!("active")),
|
|
@@ -351,8 +366,16 @@ fn conditional_creation_maintains_the_index_when_it_wins_and_not_when_it_loses()
|
|
|
351
366
|
key: "items:a".into(),
|
|
352
367
|
value: Some(json!({ "status": "active" })),
|
|
353
368
|
};
|
|
354
|
-
db.apply_atomic_transaction_guarded(
|
|
355
|
-
|
|
369
|
+
db.apply_atomic_transaction_guarded(
|
|
370
|
+
"pr35-put-1",
|
|
371
|
+
None,
|
|
372
|
+
None,
|
|
373
|
+
&[],
|
|
374
|
+
&[absent.clone()],
|
|
375
|
+
&[write.clone()],
|
|
376
|
+
None,
|
|
377
|
+
)
|
|
378
|
+
.expect("the record is absent, so the creation wins");
|
|
356
379
|
assert_eq!(ids(&db, "status", json!("active")), vec!["a".to_string()]);
|
|
357
380
|
assert_index_matches_state(&db, "a conditional creation that won");
|
|
358
381
|
|
|
@@ -520,14 +543,17 @@ fn declaring_an_index_populates_it_from_state_that_already_exists() {
|
|
|
520
543
|
None,
|
|
521
544
|
"before declaration there is no index to consult",
|
|
522
545
|
);
|
|
523
|
-
assert!(db
|
|
546
|
+
assert!(db
|
|
547
|
+
.create_equality_index("items", "status")
|
|
548
|
+
.expect("declare"));
|
|
524
549
|
assert_eq!(
|
|
525
550
|
ids(&db, "status", json!("active")),
|
|
526
551
|
vec!["a".to_string()],
|
|
527
552
|
"declaration derives the index from the records already held",
|
|
528
553
|
);
|
|
529
554
|
assert!(
|
|
530
|
-
!db.create_equality_index("items", "status")
|
|
555
|
+
!db.create_equality_index("items", "status")
|
|
556
|
+
.expect("declare"),
|
|
531
557
|
"re-declaring an existing index reports that it created nothing",
|
|
532
558
|
);
|
|
533
559
|
assert_index_matches_state(&db, "declaring an index over existing state");
|
|
@@ -548,8 +574,11 @@ fn an_explicit_rebuild_reproduces_the_maintained_index_exactly() {
|
|
|
548
574
|
);
|
|
549
575
|
}
|
|
550
576
|
db.delete("items:record-000").expect("delete");
|
|
551
|
-
db.update(
|
|
552
|
-
|
|
577
|
+
db.update(
|
|
578
|
+
"items:record-001",
|
|
579
|
+
&json!({ "status": "active", "tenantId": "tenant-9" }),
|
|
580
|
+
)
|
|
581
|
+
.expect("update");
|
|
553
582
|
|
|
554
583
|
let maintained = snapshot(&db);
|
|
555
584
|
db.rebuild_equality_indexes().expect("rebuild");
|
|
@@ -567,8 +596,10 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
|
|
|
567
596
|
|
|
568
597
|
let before = {
|
|
569
598
|
let db = FeltDb::open(&path).expect("open");
|
|
570
|
-
db.create_equality_index("items", "status")
|
|
571
|
-
|
|
599
|
+
db.create_equality_index("items", "status")
|
|
600
|
+
.expect("declare");
|
|
601
|
+
db.create_equality_index("items", "tenantId")
|
|
602
|
+
.expect("declare");
|
|
572
603
|
for step in 0..48 {
|
|
573
604
|
db.insert(
|
|
574
605
|
&format!("items:record-{step:03}"),
|
|
@@ -577,8 +608,11 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
|
|
|
577
608
|
.expect("insert");
|
|
578
609
|
}
|
|
579
610
|
db.delete("items:record-005").expect("delete");
|
|
580
|
-
db.update(
|
|
581
|
-
|
|
611
|
+
db.update(
|
|
612
|
+
"items:record-006",
|
|
613
|
+
&json!({ "status": "active", "tenantId": "tenant-0" }),
|
|
614
|
+
)
|
|
615
|
+
.expect("update");
|
|
582
616
|
db.apply_atomic_transaction_guarded(
|
|
583
617
|
"pr35-restart-tx",
|
|
584
618
|
None,
|
|
@@ -587,7 +621,10 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
|
|
|
587
621
|
&[],
|
|
588
622
|
&[
|
|
589
623
|
mutation("items:record-007", None),
|
|
590
|
-
mutation(
|
|
624
|
+
mutation(
|
|
625
|
+
"items:record-100",
|
|
626
|
+
Some(json!({ "status": "active", "tenantId": "tenant-1" })),
|
|
627
|
+
),
|
|
591
628
|
],
|
|
592
629
|
None,
|
|
593
630
|
)
|
|
@@ -600,8 +637,12 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
|
|
|
600
637
|
reopened.equality_indexes().expect("indexes").is_empty(),
|
|
601
638
|
"an index is derived state: nothing about it is read back from the log",
|
|
602
639
|
);
|
|
603
|
-
reopened
|
|
604
|
-
|
|
640
|
+
reopened
|
|
641
|
+
.create_equality_index("items", "status")
|
|
642
|
+
.expect("declare");
|
|
643
|
+
reopened
|
|
644
|
+
.create_equality_index("items", "tenantId")
|
|
645
|
+
.expect("declare");
|
|
605
646
|
|
|
606
647
|
assert_eq!(
|
|
607
648
|
snapshot(&reopened),
|
|
@@ -699,8 +740,11 @@ fn indexed_execution_returns_exactly_what_the_scan_returns() {
|
|
|
699
740
|
|
|
700
741
|
// The conjunction actually narrows rather than merely re-filtering: the
|
|
701
742
|
// intersection is smaller than either side.
|
|
702
|
-
let both = indexed_query(
|
|
703
|
-
|
|
743
|
+
let both = indexed_query(
|
|
744
|
+
&db,
|
|
745
|
+
&[("status", json!("active")), ("tenantId", json!("tenant-1"))],
|
|
746
|
+
)
|
|
747
|
+
.expect("both fields are indexed");
|
|
704
748
|
let one = indexed_query(&db, &[("status", json!("active"))]).expect("indexed");
|
|
705
749
|
assert!(!both.is_empty() && both.len() < one.len());
|
|
706
750
|
}
|
|
@@ -718,8 +762,11 @@ fn the_index_selects_candidates_and_the_predicate_still_decides() {
|
|
|
718
762
|
"the index answers with candidates, both of which satisfy only the indexed condition",
|
|
719
763
|
);
|
|
720
764
|
|
|
721
|
-
let result = indexed_query(
|
|
722
|
-
|
|
765
|
+
let result = indexed_query(
|
|
766
|
+
&db,
|
|
767
|
+
&[("status", json!("active")), ("role", json!("admin"))],
|
|
768
|
+
)
|
|
769
|
+
.expect("indexed");
|
|
723
770
|
assert_eq!(
|
|
724
771
|
result,
|
|
725
772
|
vec!["items:a".to_string()],
|
|
@@ -834,14 +881,19 @@ fn concurrent_mutation_and_indexed_query_stay_coherent() {
|
|
|
834
881
|
// mean a partially committed transaction became queryable.
|
|
835
882
|
for (sibling, count) in halves {
|
|
836
883
|
if count != 2 {
|
|
837
|
-
failures.push(format!(
|
|
884
|
+
failures.push(format!(
|
|
885
|
+
"transaction pair {sibling} was observed {count} times, not 2"
|
|
886
|
+
));
|
|
838
887
|
}
|
|
839
888
|
}
|
|
840
889
|
observations += 1;
|
|
841
890
|
}
|
|
842
891
|
|
|
843
892
|
running.store(false, Ordering::Relaxed);
|
|
844
|
-
let cycles: usize = writers
|
|
893
|
+
let cycles: usize = writers
|
|
894
|
+
.into_iter()
|
|
895
|
+
.map(|handle| handle.join().expect("writer"))
|
|
896
|
+
.sum();
|
|
845
897
|
|
|
846
898
|
assert!(cycles > 0, "the writers actually mutated the collection");
|
|
847
899
|
assert!(observations > 0, "the reader actually queried");
|
|
@@ -881,7 +933,10 @@ fn indexed_and_scan_execution_agree_on_a_concurrently_built_state() {
|
|
|
881
933
|
for conditions in [
|
|
882
934
|
vec![("status", json!("active"))],
|
|
883
935
|
vec![("tenantId", json!("tenant-3"))],
|
|
884
|
-
vec![
|
|
936
|
+
vec![
|
|
937
|
+
("status", json!("archived")),
|
|
938
|
+
("tenantId", json!("tenant-2")),
|
|
939
|
+
],
|
|
885
940
|
] {
|
|
886
941
|
assert_eq!(
|
|
887
942
|
indexed_query(&db, &conditions).expect("indexed"),
|
|
@@ -17,10 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
use feltdb::{
|
|
19
19
|
state_contract::{
|
|
20
|
-
|
|
21
|
-
FieldConstraints,
|
|
22
|
-
AuthorizationContext, CanonicalQuery, begin_read, execute_query,
|
|
23
|
-
QueryFilter,
|
|
20
|
+
begin_read, execute_query, AuthorizationContext, CanonicalQuery, CollectionSchema,
|
|
21
|
+
FieldConstraints, FieldSchema, FieldType, PrimitiveType, QueryFilter, StateSchema,
|
|
24
22
|
},
|
|
25
23
|
FeltDb,
|
|
26
24
|
};
|
|
@@ -324,8 +322,7 @@ fn pr7_phase3_self_authorization_proof() -> Result<(), Box<dyn std::error::Error
|
|
|
324
322
|
field_projections: Default::default(),
|
|
325
323
|
};
|
|
326
324
|
|
|
327
|
-
let _bob_context = begin_read(&db, &schema, "app", bob_auth)
|
|
328
|
-
.map_err(|e| format!("{:?}", e))?;
|
|
325
|
+
let _bob_context = begin_read(&db, &schema, "app", bob_auth).map_err(|e| format!("{:?}", e))?;
|
|
329
326
|
|
|
330
327
|
// Query as Bob with self(user) policy
|
|
331
328
|
let _query = CanonicalQuery {
|
|
@@ -360,8 +357,8 @@ fn pr7_phase3_self_authorization_proof() -> Result<(), Box<dyn std::error::Error
|
|
|
360
357
|
field_projections: Default::default(),
|
|
361
358
|
};
|
|
362
359
|
|
|
363
|
-
let _alice_context =
|
|
364
|
-
.map_err(|e| format!("{:?}", e))?;
|
|
360
|
+
let _alice_context =
|
|
361
|
+
begin_read(&db, &schema, "app", alice_auth).map_err(|e| format!("{:?}", e))?;
|
|
365
362
|
|
|
366
363
|
println!(" • Alice is member of Org A");
|
|
367
364
|
println!(" • Invitation.organization = {}", org_a_id);
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
use feltdb::{
|
|
19
19
|
state_contract::{
|
|
20
|
-
|
|
21
|
-
FieldConstraints,
|
|
20
|
+
CollectionSchema, FieldConstraints, FieldSchema, FieldType, PrimitiveType, StateSchema,
|
|
22
21
|
},
|
|
23
22
|
FeltDb,
|
|
24
23
|
};
|
|
@@ -150,10 +149,7 @@ fn pr8_resource_inventory_and_pattern_mapping() -> Result<(), Box<dyn std::error
|
|
|
150
149
|
for primitive in primitives {
|
|
151
150
|
let count = resources
|
|
152
151
|
.iter()
|
|
153
|
-
.filter(|r|
|
|
154
|
-
r.read_policy.contains(primitive)
|
|
155
|
-
|| r.write_policy.contains(primitive)
|
|
156
|
-
})
|
|
152
|
+
.filter(|r| r.read_policy.contains(primitive) || r.write_policy.contains(primitive))
|
|
157
153
|
.count();
|
|
158
154
|
if count > 0 {
|
|
159
155
|
println!(" ✓ {} - Used in {} resources", primitive, count);
|
|
@@ -184,37 +180,35 @@ fn pr8_pattern_class_1_personal_identity_scoped() -> Result<(), Box<dyn std::err
|
|
|
184
180
|
schema_version: 1,
|
|
185
181
|
application_id: "saas-portal".to_string(),
|
|
186
182
|
revision_id: "rev-1".to_string(),
|
|
187
|
-
collections: vec![
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
primitive: PrimitiveType::String,
|
|
196
|
-
},
|
|
197
|
-
nullable: false,
|
|
198
|
-
required: true,
|
|
199
|
-
default: None,
|
|
200
|
-
constraints: FieldConstraints::default(),
|
|
201
|
-
computed: None,
|
|
183
|
+
collections: vec![CollectionSchema {
|
|
184
|
+
name: "User".to_string(),
|
|
185
|
+
version: 1,
|
|
186
|
+
fields: vec![
|
|
187
|
+
FieldSchema {
|
|
188
|
+
name: "_id".to_string(),
|
|
189
|
+
field_type: FieldType::Primitive {
|
|
190
|
+
primitive: PrimitiveType::String,
|
|
202
191
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
192
|
+
nullable: false,
|
|
193
|
+
required: true,
|
|
194
|
+
default: None,
|
|
195
|
+
constraints: FieldConstraints::default(),
|
|
196
|
+
computed: None,
|
|
197
|
+
},
|
|
198
|
+
FieldSchema {
|
|
199
|
+
name: "email".to_string(),
|
|
200
|
+
field_type: FieldType::Primitive {
|
|
201
|
+
primitive: PrimitiveType::String,
|
|
213
202
|
},
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
203
|
+
nullable: false,
|
|
204
|
+
required: true,
|
|
205
|
+
default: None,
|
|
206
|
+
constraints: FieldConstraints::default(),
|
|
207
|
+
computed: None,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
indexes: vec![],
|
|
211
|
+
}],
|
|
218
212
|
};
|
|
219
213
|
|
|
220
214
|
// Setup test data
|
|
@@ -415,11 +409,17 @@ fn pr8_pattern_class_2_organizational_relational() -> Result<(), Box<dyn std::er
|
|
|
415
409
|
let org_a_id = "org-a";
|
|
416
410
|
let proj_id = "proj-1";
|
|
417
411
|
|
|
418
|
-
db.insert(
|
|
419
|
-
|
|
412
|
+
db.insert(
|
|
413
|
+
"User#user-alice",
|
|
414
|
+
json!({ "_id": alice_id, "email": "alice@example.com" }),
|
|
415
|
+
)
|
|
416
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
420
417
|
|
|
421
|
-
db.insert(
|
|
422
|
-
|
|
418
|
+
db.insert(
|
|
419
|
+
"User#user-bob",
|
|
420
|
+
json!({ "_id": bob_id, "email": "bob@example.com" }),
|
|
421
|
+
)
|
|
422
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
423
423
|
|
|
424
424
|
db.insert(
|
|
425
425
|
"Organization#org-a",
|
|
@@ -672,11 +672,17 @@ fn pr8_pattern_class_4_composition_multiple_audiences() -> Result<(), Box<dyn st
|
|
|
672
672
|
let org_a_id = "org-a";
|
|
673
673
|
let inv_id = "inv-1";
|
|
674
674
|
|
|
675
|
-
db.insert(
|
|
676
|
-
|
|
675
|
+
db.insert(
|
|
676
|
+
"User#user-alice",
|
|
677
|
+
json!({ "_id": alice_id, "email": "alice@example.com" }),
|
|
678
|
+
)
|
|
679
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
677
680
|
|
|
678
|
-
db.insert(
|
|
679
|
-
|
|
681
|
+
db.insert(
|
|
682
|
+
"User#user-bob",
|
|
683
|
+
json!({ "_id": bob_id, "email": "bob@example.com" }),
|
|
684
|
+
)
|
|
685
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
680
686
|
|
|
681
687
|
db.insert(
|
|
682
688
|
"Organization#org-a",
|
|
@@ -853,7 +859,9 @@ fn pr8_final_conclusion() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
853
859
|
|
|
854
860
|
println!("RESULTS\n");
|
|
855
861
|
println!(" Gaps: NONE");
|
|
856
|
-
println!(
|
|
862
|
+
println!(
|
|
863
|
+
" Redundancies: owner (minor - can be expressed as self, but kept for convenience)\n"
|
|
864
|
+
);
|
|
857
865
|
|
|
858
866
|
println!("VERDICT: VOCABULARY IS COMPLETE ✓\n");
|
|
859
867
|
|
|
@@ -22,15 +22,14 @@
|
|
|
22
22
|
|
|
23
23
|
use feltdb::{
|
|
24
24
|
state_contract::{
|
|
25
|
-
|
|
26
|
-
FieldConstraints,
|
|
27
|
-
CanonicalQuery, QueryFilter,
|
|
25
|
+
begin_read, execute_query, AuthorizationContext, CanonicalQuery, CollectionSchema,
|
|
26
|
+
FieldConstraints, FieldSchema, FieldType, PrimitiveType, QueryFilter, StateSchema,
|
|
28
27
|
},
|
|
29
28
|
FeltDb,
|
|
30
29
|
};
|
|
31
30
|
use serde_json::{json, Value};
|
|
32
|
-
use tempfile::TempDir;
|
|
33
31
|
use std::fmt;
|
|
32
|
+
use tempfile::TempDir;
|
|
34
33
|
|
|
35
34
|
/// Result of testing an operation at the FeltDB boundary
|
|
36
35
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
@@ -322,19 +321,31 @@ impl TeamBoundaryTestFixture {
|
|
|
322
321
|
let team_id = "team-backend".to_string();
|
|
323
322
|
|
|
324
323
|
// Create users
|
|
325
|
-
db.insert(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
324
|
+
db.insert(
|
|
325
|
+
"User#user-owner",
|
|
326
|
+
json!({ "_id": owner_id, "email": "owner@example.com" }),
|
|
327
|
+
)
|
|
328
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
329
|
+
db.insert(
|
|
330
|
+
"User#user-admin",
|
|
331
|
+
json!({ "_id": admin_id, "email": "admin@example.com" }),
|
|
332
|
+
)
|
|
333
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
334
|
+
db.insert(
|
|
335
|
+
"User#user-member",
|
|
336
|
+
json!({ "_id": member_id, "email": "member@example.com" }),
|
|
337
|
+
)
|
|
338
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
331
339
|
db.insert(
|
|
332
340
|
"User#user-non-member",
|
|
333
341
|
json!({ "_id": non_member_id, "email": "nonmember@example.com" }),
|
|
334
342
|
)
|
|
335
343
|
.map_err(|e| format!("{:?}", e))?;
|
|
336
|
-
db.insert(
|
|
337
|
-
|
|
344
|
+
db.insert(
|
|
345
|
+
"User#user-revoked",
|
|
346
|
+
json!({ "_id": revoked_id, "email": "revoked@example.com" }),
|
|
347
|
+
)
|
|
348
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
338
349
|
db.insert(
|
|
339
350
|
"User#user-cross-tenant",
|
|
340
351
|
json!({ "_id": cross_tenant_id, "email": "cross@example.com" }),
|
|
@@ -342,10 +353,16 @@ impl TeamBoundaryTestFixture {
|
|
|
342
353
|
.map_err(|e| format!("{:?}", e))?;
|
|
343
354
|
|
|
344
355
|
// Create organizations
|
|
345
|
-
db.insert(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
356
|
+
db.insert(
|
|
357
|
+
"Organization#org-main",
|
|
358
|
+
json!({ "_id": org_id, "name": "Main Org" }),
|
|
359
|
+
)
|
|
360
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
361
|
+
db.insert(
|
|
362
|
+
"Organization#org-other",
|
|
363
|
+
json!({ "_id": cross_org_id, "name": "Other Org" }),
|
|
364
|
+
)
|
|
365
|
+
.map_err(|e| format!("{:?}", e))?;
|
|
349
366
|
|
|
350
367
|
// Create org memberships (establishes organization access)
|
|
351
368
|
db.insert(
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
use feltdb::{
|
|
20
20
|
state_contract::{
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
AuthorizationContext, CollectionSchema, FieldConstraints, FieldSchema, FieldType,
|
|
22
|
+
PrimitiveType, StateSchema,
|
|
23
23
|
},
|
|
24
24
|
FeltDb,
|
|
25
25
|
};
|
|
@@ -66,13 +66,28 @@ impl PathATestFixture {
|
|
|
66
66
|
let team_id = "team-backend".to_string();
|
|
67
67
|
|
|
68
68
|
// Create users
|
|
69
|
-
db.insert(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
db.insert(
|
|
70
|
+
"User#user-owner",
|
|
71
|
+
json!({"_id": owner_id, "email": "owner@example.com"}),
|
|
72
|
+
)?;
|
|
73
|
+
db.insert(
|
|
74
|
+
"User#user-admin",
|
|
75
|
+
json!({"_id": admin_id, "email": "admin@example.com"}),
|
|
76
|
+
)?;
|
|
77
|
+
db.insert(
|
|
78
|
+
"User#user-member",
|
|
79
|
+
json!({"_id": member_id, "email": "member@example.com"}),
|
|
80
|
+
)?;
|
|
81
|
+
db.insert(
|
|
82
|
+
"User#user-non-member",
|
|
83
|
+
json!({"_id": non_member_id, "email": "non-member@example.com"}),
|
|
84
|
+
)?;
|
|
73
85
|
|
|
74
86
|
// Create organization
|
|
75
|
-
db.insert(
|
|
87
|
+
db.insert(
|
|
88
|
+
"Organization#org-1",
|
|
89
|
+
json!({"_id": org_id, "name": "Org 1"}),
|
|
90
|
+
)?;
|
|
76
91
|
|
|
77
92
|
// Create memberships for all users in the organization
|
|
78
93
|
db.insert(
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
//! Test that the new authorized mutation API properly enforces authorization.
|
|
4
4
|
//! Authorization evaluation happens INSIDE the FeltDB boundary, before mutations are applied.
|
|
5
5
|
|
|
6
|
-
use feltdb::{
|
|
7
|
-
state_contract::AuthorizationContext,
|
|
8
|
-
FeltDb,
|
|
9
|
-
};
|
|
6
|
+
use feltdb::{state_contract::AuthorizationContext, FeltDb};
|
|
10
7
|
use serde_json::json;
|
|
11
8
|
use tempfile::TempDir;
|
|
12
9
|
|
|
@@ -43,7 +40,11 @@ fn authorized_insert_requires_state_write_capability() -> Result<(), Box<dyn std
|
|
|
43
40
|
json!({"_id": "user-1", "name": "Alice"}),
|
|
44
41
|
&authorized_context,
|
|
45
42
|
);
|
|
46
|
-
assert!(
|
|
43
|
+
assert!(
|
|
44
|
+
result.is_ok(),
|
|
45
|
+
"Authorized insert should succeed: {:?}",
|
|
46
|
+
result
|
|
47
|
+
);
|
|
47
48
|
|
|
48
49
|
// Verify record was inserted
|
|
49
50
|
let value = db.get::<serde_json::Value>("User#user-1")?;
|
|
@@ -63,7 +64,10 @@ fn authorized_insert_requires_state_write_capability() -> Result<(), Box<dyn std
|
|
|
63
64
|
|
|
64
65
|
// Verify unauthorized insert did not create record
|
|
65
66
|
let value = db.get::<serde_json::Value>("User#user-2")?;
|
|
66
|
-
assert!(
|
|
67
|
+
assert!(
|
|
68
|
+
value.is_none(),
|
|
69
|
+
"Unauthorized insert should not create record"
|
|
70
|
+
);
|
|
67
71
|
|
|
68
72
|
Ok(())
|
|
69
73
|
}
|
|
@@ -123,11 +127,18 @@ fn authorized_delete_requires_state_write_capability() -> Result<(), Box<dyn std
|
|
|
123
127
|
|
|
124
128
|
// Authorized delete should succeed
|
|
125
129
|
let result = db.delete_with_authorization("Team#team-1", &authorized_context);
|
|
126
|
-
assert!(
|
|
130
|
+
assert!(
|
|
131
|
+
result.is_ok(),
|
|
132
|
+
"Authorized delete should succeed: {:?}",
|
|
133
|
+
result
|
|
134
|
+
);
|
|
127
135
|
|
|
128
136
|
// Verify record is deleted
|
|
129
137
|
let value = db.get::<serde_json::Value>("Team#team-1")?;
|
|
130
|
-
assert!(
|
|
138
|
+
assert!(
|
|
139
|
+
value.is_none(),
|
|
140
|
+
"Record should be deleted after authorized delete"
|
|
141
|
+
);
|
|
131
142
|
|
|
132
143
|
Ok(())
|
|
133
144
|
}
|
|
@@ -167,7 +178,13 @@ fn authorized_update_requires_state_write_capability() -> Result<(), Box<dyn std
|
|
|
167
178
|
)?;
|
|
168
179
|
|
|
169
180
|
let original = db.get::<serde_json::Value>("Organization#org-1")?;
|
|
170
|
-
assert_eq!(
|
|
181
|
+
assert_eq!(
|
|
182
|
+
original
|
|
183
|
+
.as_ref()
|
|
184
|
+
.and_then(|v| v.get("name"))
|
|
185
|
+
.and_then(|v| v.as_str()),
|
|
186
|
+
Some("Acme Corp")
|
|
187
|
+
);
|
|
171
188
|
|
|
172
189
|
// Unauthorized update should fail
|
|
173
190
|
let result = db.update_with_authorization(
|
|
@@ -184,7 +201,10 @@ fn authorized_update_requires_state_write_capability() -> Result<(), Box<dyn std
|
|
|
184
201
|
// Verify record was not changed
|
|
185
202
|
let value = db.get::<serde_json::Value>("Organization#org-1")?;
|
|
186
203
|
assert_eq!(
|
|
187
|
-
value
|
|
204
|
+
value
|
|
205
|
+
.as_ref()
|
|
206
|
+
.and_then(|v| v.get("name"))
|
|
207
|
+
.and_then(|v| v.as_str()),
|
|
188
208
|
Some("Acme Corp"),
|
|
189
209
|
"Unauthorized update should not change record"
|
|
190
210
|
);
|
|
@@ -195,12 +215,19 @@ fn authorized_update_requires_state_write_capability() -> Result<(), Box<dyn std
|
|
|
195
215
|
json!({"_id": "org-1", "name": "Acme Inc"}),
|
|
196
216
|
&authorized_context,
|
|
197
217
|
);
|
|
198
|
-
assert!(
|
|
218
|
+
assert!(
|
|
219
|
+
result.is_ok(),
|
|
220
|
+
"Authorized update should succeed: {:?}",
|
|
221
|
+
result
|
|
222
|
+
);
|
|
199
223
|
|
|
200
224
|
// Verify record was updated
|
|
201
225
|
let value = db.get::<serde_json::Value>("Organization#org-1")?;
|
|
202
226
|
assert_eq!(
|
|
203
|
-
value
|
|
227
|
+
value
|
|
228
|
+
.as_ref()
|
|
229
|
+
.and_then(|v| v.get("name"))
|
|
230
|
+
.and_then(|v| v.as_str()),
|
|
204
231
|
Some("Acme Inc"),
|
|
205
232
|
"Authorized update should change record"
|
|
206
233
|
);
|
|
@@ -276,16 +303,8 @@ fn atomic_transaction_with_authorization_denies_on_any_mutation_failure(
|
|
|
276
303
|
};
|
|
277
304
|
|
|
278
305
|
// Create initial records
|
|
279
|
-
db.insert_with_authorization(
|
|
280
|
-
|
|
281
|
-
json!({"_id": "user-1"}),
|
|
282
|
-
&authorized_context,
|
|
283
|
-
)?;
|
|
284
|
-
db.insert_with_authorization(
|
|
285
|
-
"Team#team-1",
|
|
286
|
-
json!({"_id": "team-1"}),
|
|
287
|
-
&authorized_context,
|
|
288
|
-
)?;
|
|
306
|
+
db.insert_with_authorization("User#user-1", json!({"_id": "user-1"}), &authorized_context)?;
|
|
307
|
+
db.insert_with_authorization("Team#team-1", json!({"_id": "team-1"}), &authorized_context)?;
|
|
289
308
|
|
|
290
309
|
// Attempt atomic transaction with unauthorized context
|
|
291
310
|
// Should be rejected without applying ANY mutations
|