@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.
Files changed (149) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/Cargo.lock +165 -0
  3. package/dist/create/server-source/Cargo.toml +9 -0
  4. package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
  5. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
  6. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
  7. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
  8. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
  9. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
  10. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
  11. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
  12. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
  13. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
  14. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
  15. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
  19. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
  20. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
  21. package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
  22. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
  23. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
  24. package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
  25. package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
  26. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
  27. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
  28. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
  29. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
  30. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
  31. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
  32. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
  33. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
  34. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
  35. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
  36. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
  37. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
  38. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
  39. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
  40. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
  41. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
  42. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
  43. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
  44. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
  45. package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
  46. package/dist/create/server-source/crates/feltdb/src/lib.rs +507 -6
  47. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
  48. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
  49. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
  50. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
  51. package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
  52. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
  53. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
  54. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
  55. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
  56. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
  57. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
  58. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
  59. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
  60. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
  61. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
  62. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
  63. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
  64. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
  65. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
  66. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
  67. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
  68. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
  69. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
  70. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
  71. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
  72. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
  73. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
  74. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
  75. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
  76. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
  77. package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
  78. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
  79. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
  80. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
  81. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
  82. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
  83. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
  84. package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
  85. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
  86. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
  87. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
  88. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
  89. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
  90. package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
  91. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
  92. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
  93. package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
  94. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
  95. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
  96. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
  97. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
  98. package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
  99. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
  100. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  101. package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
  102. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
  103. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
  104. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
  105. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
  106. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
  107. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
  108. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
  109. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
  110. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
  111. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
  112. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
  113. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
  114. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
  115. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
  116. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
  117. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
  118. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
  119. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
  120. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
  121. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
  122. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
  123. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
  124. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
  125. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
  126. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
  127. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
  128. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
  129. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
  130. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
  131. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
  132. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
  133. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
  134. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
  135. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
  136. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
  137. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
  139. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
  140. package/dist/create/server-source/crates/feltdb-server/src/main.rs +326 -6
  141. package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-DaNwCLRX.js} +1 -1
  142. package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
  143. package/dist/studio-app/assets/{index-DwgNAIIX.js → index-j8IlhNqJ.js} +1 -1
  144. package/dist/studio-app/index.html +1 -1
  145. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  146. package/dist/workload.d.ts +2 -1
  147. package/dist/workload.d.ts.map +1 -1
  148. package/package.json +1 -1
  149. package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
@@ -123,7 +123,9 @@ impl PolicySubject {
123
123
  /// Returns None if the subject is not recognized.
124
124
  pub fn from_str(s: &str) -> Option<Self> {
125
125
  if let Some(field) = s.strip_prefix("self(").and_then(|s| s.strip_suffix(")")) {
126
- return Some(PolicySubject::Self_ { field: field.to_string() });
126
+ return Some(PolicySubject::Self_ {
127
+ field: field.to_string(),
128
+ });
127
129
  }
128
130
  match s {
129
131
  "authenticated" => Some(PolicySubject::Authenticated),
@@ -222,9 +224,7 @@ impl AuthorizationState {
222
224
  let capability = format!("{}:{}", self.state_namespace, collection);
223
225
  self.snapshot_rows
224
226
  .iter()
225
- .find(|row| {
226
- row.capability == capability && row.key == key && !row.deleted
227
- })
227
+ .find(|row| row.capability == capability && row.key == key && !row.deleted)
228
228
  .map(|row| row.value.clone())
229
229
  }
230
230
 
@@ -240,21 +240,28 @@ impl AuthorizationState {
240
240
 
241
241
  /// Find a field in a collection that references another collection.
242
242
  /// Returns the field name if found.
243
- pub fn find_reference_field(&self, from_collection: &str, to_collection: &str) -> Option<String> {
244
- let collection = self.schema.collections
243
+ pub fn find_reference_field(
244
+ &self,
245
+ from_collection: &str,
246
+ to_collection: &str,
247
+ ) -> Option<String> {
248
+ let collection = self
249
+ .schema
250
+ .collections
245
251
  .iter()
246
252
  .find(|c| c.name == from_collection)?;
247
253
 
248
- collection.fields
249
- .iter()
250
- .find_map(|field| {
251
- if let crate::state_contract::FieldType::Reference { collection: ref_col } = &field.field_type {
252
- if ref_col == to_collection {
253
- return Some(field.name.clone());
254
- }
254
+ collection.fields.iter().find_map(|field| {
255
+ if let crate::state_contract::FieldType::Reference {
256
+ collection: ref_col,
257
+ } = &field.field_type
258
+ {
259
+ if ref_col == to_collection {
260
+ return Some(field.name.clone());
255
261
  }
256
- None
257
- })
262
+ }
263
+ None
264
+ })
258
265
  }
259
266
 
260
267
  /// Find which collection contains membership information (references both User and Organization).
@@ -263,7 +270,10 @@ impl AuthorizationState {
263
270
  // Look for a collection that has references to both User and Organization
264
271
  for collection in &self.schema.collections {
265
272
  let has_user_ref = collection.fields.iter().any(|f| {
266
- if let crate::state_contract::FieldType::Reference { collection: ref_col } = &f.field_type {
273
+ if let crate::state_contract::FieldType::Reference {
274
+ collection: ref_col,
275
+ } = &f.field_type
276
+ {
267
277
  ref_col == "User" || ref_col == "Users"
268
278
  } else {
269
279
  false
@@ -271,7 +281,10 @@ impl AuthorizationState {
271
281
  });
272
282
 
273
283
  let has_org_ref = collection.fields.iter().any(|f| {
274
- if let crate::state_contract::FieldType::Reference { collection: ref_col } = &f.field_type {
284
+ if let crate::state_contract::FieldType::Reference {
285
+ collection: ref_col,
286
+ } = &f.field_type
287
+ {
275
288
  ref_col == "Organization" || ref_col == "Organizations"
276
289
  } else {
277
290
  false
@@ -288,7 +301,9 @@ impl AuthorizationState {
288
301
  /// Find field names for user and organization references within a collection.
289
302
  /// Returns (user_field, org_field) if both are found.
290
303
  pub fn find_membership_fields(&self, collection_name: &str) -> Option<(String, String)> {
291
- let collection = self.schema.collections
304
+ let collection = self
305
+ .schema
306
+ .collections
292
307
  .iter()
293
308
  .find(|c| c.name == collection_name)?;
294
309
 
@@ -296,11 +311,15 @@ impl AuthorizationState {
296
311
  let mut org_field = None;
297
312
 
298
313
  for field in &collection.fields {
299
- if let crate::state_contract::FieldType::Reference { collection: ref_col } = &field.field_type {
314
+ if let crate::state_contract::FieldType::Reference {
315
+ collection: ref_col,
316
+ } = &field.field_type
317
+ {
300
318
  if (ref_col == "User" || ref_col == "Users") && user_field.is_none() {
301
319
  user_field = Some(field.name.clone());
302
320
  }
303
- if (ref_col == "Organization" || ref_col == "Organizations") && org_field.is_none() {
321
+ if (ref_col == "Organization" || ref_col == "Organizations") && org_field.is_none()
322
+ {
304
323
  org_field = Some(field.name.clone());
305
324
  }
306
325
  }
@@ -387,12 +406,10 @@ impl PolicyEvaluator {
387
406
  PolicySubject::Authenticated => Self::evaluate_authenticated(context),
388
407
  PolicySubject::Owner => Self::evaluate_owner(context),
389
408
  PolicySubject::Member => Self::evaluate_member(context),
390
- PolicySubject::Self_ { .. } => {
391
- Err(PolicyAuthorizationError::new(
392
- "INVALID_POLICY_CONTEXT",
393
- "self policies require record-level context; use evaluate_record instead",
394
- ))
395
- }
409
+ PolicySubject::Self_ { .. } => Err(PolicyAuthorizationError::new(
410
+ "INVALID_POLICY_CONTEXT",
411
+ "self policies require record-level context; use evaluate_record instead",
412
+ )),
396
413
  }
397
414
  }
398
415
 
@@ -448,12 +465,10 @@ impl PolicyEvaluator {
448
465
  if actor.id == *owner_id {
449
466
  Ok(AuthorizationDecision::Allow)
450
467
  } else {
451
- Err(PolicyAuthorizationError::unauthorized(
452
- format!(
453
- "actor {} is not the owner ({})",
454
- actor.id, owner_id
455
- ),
456
- ))
468
+ Err(PolicyAuthorizationError::unauthorized(format!(
469
+ "actor {} is not the owner ({})",
470
+ actor.id, owner_id
471
+ )))
457
472
  }
458
473
  }
459
474
 
@@ -474,19 +489,19 @@ impl PolicyEvaluator {
474
489
  )
475
490
  })?;
476
491
 
477
- let is_member = context.resource_attributes.get("is_member")
492
+ let is_member = context
493
+ .resource_attributes
494
+ .get("is_member")
478
495
  .map(|v| v == "true")
479
496
  .unwrap_or(false);
480
497
 
481
498
  if is_member {
482
499
  Ok(AuthorizationDecision::Allow)
483
500
  } else {
484
- Err(PolicyAuthorizationError::unauthorized(
485
- format!(
486
- "actor {} is not a member of organization {}",
487
- actor.id, organization_id
488
- ),
489
- ))
501
+ Err(PolicyAuthorizationError::unauthorized(format!(
502
+ "actor {} is not a member of organization {}",
503
+ actor.id, organization_id
504
+ )))
490
505
  }
491
506
  }
492
507
 
@@ -530,12 +545,10 @@ impl PolicyEvaluator {
530
545
  if actor.id == owner_id {
531
546
  Ok(AuthorizationDecision::Allow)
532
547
  } else {
533
- Err(PolicyAuthorizationError::unauthorized(
534
- format!(
535
- "actor {} is not the owner ({})",
536
- actor.id, owner_id
537
- ),
538
- ))
548
+ Err(PolicyAuthorizationError::unauthorized(format!(
549
+ "actor {} is not the owner ({})",
550
+ actor.id, owner_id
551
+ )))
539
552
  }
540
553
  }
541
554
 
@@ -559,7 +572,8 @@ impl PolicyEvaluator {
559
572
  })?;
560
573
 
561
574
  // Find the reference field from this collection to Organization
562
- let org_ref_field = auth_state.find_reference_field(&context.collection, "Organization")
575
+ let org_ref_field = auth_state
576
+ .find_reference_field(&context.collection, "Organization")
563
577
  .or_else(|| auth_state.find_reference_field(&context.collection, "Organizations"))
564
578
  .ok_or_else(|| {
565
579
  PolicyAuthorizationError::new(
@@ -569,7 +583,8 @@ impl PolicyEvaluator {
569
583
  })?;
570
584
 
571
585
  // Extract the organization ID from the record
572
- let organization_id = context.field(&org_ref_field)
586
+ let organization_id = context
587
+ .field(&org_ref_field)
573
588
  .and_then(|v| v.as_str())
574
589
  .ok_or_else(|| {
575
590
  PolicyAuthorizationError::new(
@@ -588,7 +603,8 @@ impl PolicyEvaluator {
588
603
  })?;
589
604
 
590
605
  // Find the field names for user and organization references in the membership collection
591
- let (user_field, org_field) = auth_state.find_membership_fields(&membership_collection)
606
+ let (user_field, org_field) = auth_state
607
+ .find_membership_fields(&membership_collection)
592
608
  .ok_or_else(|| {
593
609
  PolicyAuthorizationError::new(
594
610
  "MEMBERSHIP_NOT_DEFINED",
@@ -622,12 +638,10 @@ impl PolicyEvaluator {
622
638
  if has_membership {
623
639
  Ok(AuthorizationDecision::Allow)
624
640
  } else {
625
- Err(PolicyAuthorizationError::unauthorized(
626
- format!(
627
- "actor {} is not a member of organization {}",
628
- actor.id, organization_id
629
- ),
630
- ))
641
+ Err(PolicyAuthorizationError::unauthorized(format!(
642
+ "actor {} is not a member of organization {}",
643
+ actor.id, organization_id
644
+ )))
631
645
  }
632
646
  }
633
647
 
@@ -639,12 +653,11 @@ impl PolicyEvaluator {
639
653
  context: &RecordAuthorizationContext,
640
654
  ) -> Result<AuthorizationDecision, PolicyAuthorizationError> {
641
655
  let actor = context.actor.as_ref().ok_or_else(|| {
642
- PolicyAuthorizationError::unauthenticated(
643
- "self policy requires an authenticated actor",
644
- )
656
+ PolicyAuthorizationError::unauthenticated("self policy requires an authenticated actor")
645
657
  })?;
646
658
 
647
- let field_value = context.field(field)
659
+ let field_value = context
660
+ .field(field)
648
661
  .and_then(|v| v.as_str())
649
662
  .ok_or_else(|| {
650
663
  PolicyAuthorizationError::new(
@@ -656,12 +669,10 @@ impl PolicyEvaluator {
656
669
  if actor.id == field_value {
657
670
  Ok(AuthorizationDecision::Allow)
658
671
  } else {
659
- Err(PolicyAuthorizationError::unauthorized(
660
- format!(
661
- "actor {} does not match identity field {}",
662
- actor.id, field
663
- ),
664
- ))
672
+ Err(PolicyAuthorizationError::unauthorized(format!(
673
+ "actor {} does not match identity field {}",
674
+ actor.id, field
675
+ )))
665
676
  }
666
677
  }
667
678
  }
@@ -720,9 +731,15 @@ mod tests {
720
731
 
721
732
  #[test]
722
733
  fn policy_subject_from_str() {
723
- assert_eq!(PolicySubject::from_str("authenticated"), Some(PolicySubject::Authenticated));
734
+ assert_eq!(
735
+ PolicySubject::from_str("authenticated"),
736
+ Some(PolicySubject::Authenticated)
737
+ );
724
738
  assert_eq!(PolicySubject::from_str("owner"), Some(PolicySubject::Owner));
725
- assert_eq!(PolicySubject::from_str("member"), Some(PolicySubject::Member));
739
+ assert_eq!(
740
+ PolicySubject::from_str("member"),
741
+ Some(PolicySubject::Member)
742
+ );
726
743
  assert_eq!(PolicySubject::from_str("invalid"), None);
727
744
  }
728
745
 
@@ -932,12 +949,8 @@ mod tests {
932
949
  #[test]
933
950
  fn existing_owner_policy_works_with_state() {
934
951
  let record = serde_json::json!({"_id": "rec1", "owner_id": "user123"});
935
- let context = RecordAuthorizationContext::new(
936
- Some(Actor::new("user123")),
937
- "Project",
938
- "rec1",
939
- record,
940
- );
952
+ let context =
953
+ RecordAuthorizationContext::new(Some(Actor::new("user123")), "Project", "rec1", record);
941
954
 
942
955
  // Should work even though authorization_state is None
943
956
  let result = PolicyEvaluator::evaluate_record(PolicySubject::Owner, &context);
@@ -947,12 +960,8 @@ mod tests {
947
960
  #[test]
948
961
  fn existing_authenticated_policy_works_with_state() {
949
962
  let record = serde_json::json!({"_id": "rec1"});
950
- let context = RecordAuthorizationContext::new(
951
- Some(Actor::new("user123")),
952
- "Project",
953
- "rec1",
954
- record,
955
- );
963
+ let context =
964
+ RecordAuthorizationContext::new(Some(Actor::new("user123")), "Project", "rec1", record);
956
965
 
957
966
  // Should work even though authorization_state is None
958
967
  let result = PolicyEvaluator::evaluate_record(PolicySubject::Authenticated, &context);
@@ -963,7 +972,9 @@ mod tests {
963
972
  // These tests verify that member policy is enforced using database state,
964
973
  // not caller-supplied attributes.
965
974
 
966
- fn make_schema_with_collections(collections: Vec<crate::state_contract::CollectionSchema>) -> Arc<crate::state_contract::StateSchema> {
975
+ fn make_schema_with_collections(
976
+ collections: Vec<crate::state_contract::CollectionSchema>,
977
+ ) -> Arc<crate::state_contract::StateSchema> {
967
978
  Arc::new(crate::state_contract::StateSchema {
968
979
  contract_version: 1,
969
980
  schema_version: 1,
@@ -973,7 +984,10 @@ mod tests {
973
984
  })
974
985
  }
975
986
 
976
- fn make_collection(name: &str, fields: Vec<crate::state_contract::FieldSchema>) -> crate::state_contract::CollectionSchema {
987
+ fn make_collection(
988
+ name: &str,
989
+ fields: Vec<crate::state_contract::FieldSchema>,
990
+ ) -> crate::state_contract::CollectionSchema {
977
991
  crate::state_contract::CollectionSchema {
978
992
  name: name.into(),
979
993
  version: 1,
@@ -982,7 +996,10 @@ mod tests {
982
996
  }
983
997
  }
984
998
 
985
- fn make_field(name: &str, field_type: crate::state_contract::FieldType) -> crate::state_contract::FieldSchema {
999
+ fn make_field(
1000
+ name: &str,
1001
+ field_type: crate::state_contract::FieldType,
1002
+ ) -> crate::state_contract::FieldSchema {
986
1003
  crate::state_contract::FieldSchema {
987
1004
  name: name.into(),
988
1005
  field_type,
@@ -999,19 +1016,51 @@ mod tests {
999
1016
  use crate::state_contract::FieldType;
1000
1017
 
1001
1018
  let schema = make_schema_with_collections(vec![
1002
- make_collection("Project", vec![
1003
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1004
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1005
- ]),
1006
- make_collection("Membership", vec![
1007
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1008
- make_field("user", FieldType::Reference { collection: "User".into() }),
1009
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1010
- ]),
1019
+ make_collection(
1020
+ "Project",
1021
+ vec![
1022
+ make_field(
1023
+ "_id",
1024
+ FieldType::Primitive {
1025
+ primitive: crate::state_contract::PrimitiveType::String,
1026
+ },
1027
+ ),
1028
+ make_field(
1029
+ "organization",
1030
+ FieldType::Reference {
1031
+ collection: "Organization".into(),
1032
+ },
1033
+ ),
1034
+ ],
1035
+ ),
1036
+ make_collection(
1037
+ "Membership",
1038
+ vec![
1039
+ make_field(
1040
+ "_id",
1041
+ FieldType::Primitive {
1042
+ primitive: crate::state_contract::PrimitiveType::String,
1043
+ },
1044
+ ),
1045
+ make_field(
1046
+ "user",
1047
+ FieldType::Reference {
1048
+ collection: "User".into(),
1049
+ },
1050
+ ),
1051
+ make_field(
1052
+ "organization",
1053
+ FieldType::Reference {
1054
+ collection: "Organization".into(),
1055
+ },
1056
+ ),
1057
+ ],
1058
+ ),
1011
1059
  ]);
1012
1060
 
1013
1061
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1014
- let membership = serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1062
+ let membership =
1063
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1015
1064
 
1016
1065
  let snapshot = Arc::new(vec![
1017
1066
  make_stored_row("Project", "proj1", project),
@@ -1036,19 +1085,51 @@ mod tests {
1036
1085
  use crate::state_contract::FieldType;
1037
1086
 
1038
1087
  let schema = make_schema_with_collections(vec![
1039
- make_collection("Project", vec![
1040
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1041
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1042
- ]),
1043
- make_collection("Membership", vec![
1044
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1045
- make_field("user", FieldType::Reference { collection: "User".into() }),
1046
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1047
- ]),
1088
+ make_collection(
1089
+ "Project",
1090
+ vec![
1091
+ make_field(
1092
+ "_id",
1093
+ FieldType::Primitive {
1094
+ primitive: crate::state_contract::PrimitiveType::String,
1095
+ },
1096
+ ),
1097
+ make_field(
1098
+ "organization",
1099
+ FieldType::Reference {
1100
+ collection: "Organization".into(),
1101
+ },
1102
+ ),
1103
+ ],
1104
+ ),
1105
+ make_collection(
1106
+ "Membership",
1107
+ vec![
1108
+ make_field(
1109
+ "_id",
1110
+ FieldType::Primitive {
1111
+ primitive: crate::state_contract::PrimitiveType::String,
1112
+ },
1113
+ ),
1114
+ make_field(
1115
+ "user",
1116
+ FieldType::Reference {
1117
+ collection: "User".into(),
1118
+ },
1119
+ ),
1120
+ make_field(
1121
+ "organization",
1122
+ FieldType::Reference {
1123
+ collection: "Organization".into(),
1124
+ },
1125
+ ),
1126
+ ],
1127
+ ),
1048
1128
  ]);
1049
1129
 
1050
1130
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1051
- let membership = serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1131
+ let membership =
1132
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1052
1133
 
1053
1134
  let snapshot = Arc::new(vec![
1054
1135
  make_stored_row("Project", "proj1", project),
@@ -1074,19 +1155,51 @@ mod tests {
1074
1155
  use crate::state_contract::FieldType;
1075
1156
 
1076
1157
  let schema = make_schema_with_collections(vec![
1077
- make_collection("Project", vec![
1078
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1079
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1080
- ]),
1081
- make_collection("Membership", vec![
1082
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1083
- make_field("user", FieldType::Reference { collection: "User".into() }),
1084
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1085
- ]),
1158
+ make_collection(
1159
+ "Project",
1160
+ vec![
1161
+ make_field(
1162
+ "_id",
1163
+ FieldType::Primitive {
1164
+ primitive: crate::state_contract::PrimitiveType::String,
1165
+ },
1166
+ ),
1167
+ make_field(
1168
+ "organization",
1169
+ FieldType::Reference {
1170
+ collection: "Organization".into(),
1171
+ },
1172
+ ),
1173
+ ],
1174
+ ),
1175
+ make_collection(
1176
+ "Membership",
1177
+ vec![
1178
+ make_field(
1179
+ "_id",
1180
+ FieldType::Primitive {
1181
+ primitive: crate::state_contract::PrimitiveType::String,
1182
+ },
1183
+ ),
1184
+ make_field(
1185
+ "user",
1186
+ FieldType::Reference {
1187
+ collection: "User".into(),
1188
+ },
1189
+ ),
1190
+ make_field(
1191
+ "organization",
1192
+ FieldType::Reference {
1193
+ collection: "Organization".into(),
1194
+ },
1195
+ ),
1196
+ ],
1197
+ ),
1086
1198
  ]);
1087
1199
 
1088
1200
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1089
- let membership = serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1201
+ let membership =
1202
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1090
1203
 
1091
1204
  let snapshot = Arc::new(vec![
1092
1205
  make_stored_row("Project", "proj1", project),
@@ -1112,20 +1225,52 @@ mod tests {
1112
1225
  use crate::state_contract::FieldType;
1113
1226
 
1114
1227
  let schema = make_schema_with_collections(vec![
1115
- make_collection("Project", vec![
1116
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1117
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1118
- ]),
1119
- make_collection("Membership", vec![
1120
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1121
- make_field("user", FieldType::Reference { collection: "User".into() }),
1122
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1123
- ]),
1228
+ make_collection(
1229
+ "Project",
1230
+ vec![
1231
+ make_field(
1232
+ "_id",
1233
+ FieldType::Primitive {
1234
+ primitive: crate::state_contract::PrimitiveType::String,
1235
+ },
1236
+ ),
1237
+ make_field(
1238
+ "organization",
1239
+ FieldType::Reference {
1240
+ collection: "Organization".into(),
1241
+ },
1242
+ ),
1243
+ ],
1244
+ ),
1245
+ make_collection(
1246
+ "Membership",
1247
+ vec![
1248
+ make_field(
1249
+ "_id",
1250
+ FieldType::Primitive {
1251
+ primitive: crate::state_contract::PrimitiveType::String,
1252
+ },
1253
+ ),
1254
+ make_field(
1255
+ "user",
1256
+ FieldType::Reference {
1257
+ collection: "User".into(),
1258
+ },
1259
+ ),
1260
+ make_field(
1261
+ "organization",
1262
+ FieldType::Reference {
1263
+ collection: "Organization".into(),
1264
+ },
1265
+ ),
1266
+ ],
1267
+ ),
1124
1268
  ]);
1125
1269
 
1126
1270
  // Alice is member of org1, project is in org2
1127
1271
  let project = serde_json::json!({"_id": "proj1", "organization": "org2"});
1128
- let membership = serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1272
+ let membership =
1273
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1129
1274
 
1130
1275
  let snapshot = Arc::new(vec![
1131
1276
  make_stored_row("Project", "proj1", project),
@@ -1149,16 +1294,15 @@ mod tests {
1149
1294
  #[test]
1150
1295
  fn member_policy_no_state_fails() {
1151
1296
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1152
- let context = RecordAuthorizationContext::new(
1153
- Some(Actor::new("alice")),
1154
- "Project",
1155
- "proj1",
1156
- project,
1157
- ); // No state attached
1297
+ let context =
1298
+ RecordAuthorizationContext::new(Some(Actor::new("alice")), "Project", "proj1", project); // No state attached
1158
1299
 
1159
1300
  let result = PolicyEvaluator::evaluate_record(PolicySubject::Member, &context);
1160
1301
  assert!(result.is_err());
1161
- assert_eq!(result.unwrap_err().code, "AUTHORIZATION_STATE_NOT_AVAILABLE");
1302
+ assert_eq!(
1303
+ result.unwrap_err().code,
1304
+ "AUTHORIZATION_STATE_NOT_AVAILABLE"
1305
+ );
1162
1306
  }
1163
1307
 
1164
1308
  #[test]
@@ -1166,21 +1310,45 @@ mod tests {
1166
1310
  use crate::state_contract::FieldType;
1167
1311
 
1168
1312
  let schema = make_schema_with_collections(vec![
1169
- make_collection("Project", vec![
1170
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1171
- // No organization reference
1172
- ]),
1173
- make_collection("Membership", vec![
1174
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1175
- make_field("user", FieldType::Reference { collection: "User".into() }),
1176
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1177
- ]),
1313
+ make_collection(
1314
+ "Project",
1315
+ vec![
1316
+ make_field(
1317
+ "_id",
1318
+ FieldType::Primitive {
1319
+ primitive: crate::state_contract::PrimitiveType::String,
1320
+ },
1321
+ ),
1322
+ // No organization reference
1323
+ ],
1324
+ ),
1325
+ make_collection(
1326
+ "Membership",
1327
+ vec![
1328
+ make_field(
1329
+ "_id",
1330
+ FieldType::Primitive {
1331
+ primitive: crate::state_contract::PrimitiveType::String,
1332
+ },
1333
+ ),
1334
+ make_field(
1335
+ "user",
1336
+ FieldType::Reference {
1337
+ collection: "User".into(),
1338
+ },
1339
+ ),
1340
+ make_field(
1341
+ "organization",
1342
+ FieldType::Reference {
1343
+ collection: "Organization".into(),
1344
+ },
1345
+ ),
1346
+ ],
1347
+ ),
1178
1348
  ]);
1179
1349
 
1180
1350
  let project = serde_json::json!({"_id": "proj1"});
1181
- let snapshot = Arc::new(vec![
1182
- make_stored_row("Project", "proj1", project),
1183
- ]);
1351
+ let snapshot = Arc::new(vec![make_stored_row("Project", "proj1", project)]);
1184
1352
 
1185
1353
  let auth_state = AuthorizationState::new(schema, "tenant:123", snapshot);
1186
1354
  let context = RecordAuthorizationContext::new(
@@ -1201,21 +1369,50 @@ mod tests {
1201
1369
  use crate::state_contract::FieldType;
1202
1370
 
1203
1371
  let schema = make_schema_with_collections(vec![
1204
- make_collection("Project", vec![
1205
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1206
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1207
- ]),
1208
- make_collection("Membership", vec![
1209
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1210
- make_field("user", FieldType::Reference { collection: "User".into() }),
1211
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1212
- ]),
1372
+ make_collection(
1373
+ "Project",
1374
+ vec![
1375
+ make_field(
1376
+ "_id",
1377
+ FieldType::Primitive {
1378
+ primitive: crate::state_contract::PrimitiveType::String,
1379
+ },
1380
+ ),
1381
+ make_field(
1382
+ "organization",
1383
+ FieldType::Reference {
1384
+ collection: "Organization".into(),
1385
+ },
1386
+ ),
1387
+ ],
1388
+ ),
1389
+ make_collection(
1390
+ "Membership",
1391
+ vec![
1392
+ make_field(
1393
+ "_id",
1394
+ FieldType::Primitive {
1395
+ primitive: crate::state_contract::PrimitiveType::String,
1396
+ },
1397
+ ),
1398
+ make_field(
1399
+ "user",
1400
+ FieldType::Reference {
1401
+ collection: "User".into(),
1402
+ },
1403
+ ),
1404
+ make_field(
1405
+ "organization",
1406
+ FieldType::Reference {
1407
+ collection: "Organization".into(),
1408
+ },
1409
+ ),
1410
+ ],
1411
+ ),
1213
1412
  ]);
1214
1413
 
1215
1414
  let project = serde_json::json!({"_id": "proj1"}); // Missing organization field
1216
- let snapshot = Arc::new(vec![
1217
- make_stored_row("Project", "proj1", project),
1218
- ]);
1415
+ let snapshot = Arc::new(vec![make_stored_row("Project", "proj1", project)]);
1219
1416
 
1220
1417
  let auth_state = AuthorizationState::new(schema, "tenant:123", snapshot);
1221
1418
  let context = RecordAuthorizationContext::new(
@@ -1236,20 +1433,54 @@ mod tests {
1236
1433
  use crate::state_contract::FieldType;
1237
1434
 
1238
1435
  let schema = make_schema_with_collections(vec![
1239
- make_collection("Project", vec![
1240
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1241
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1242
- ]),
1243
- make_collection("Membership", vec![
1244
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1245
- make_field("user", FieldType::Reference { collection: "User".into() }),
1246
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1247
- ]),
1436
+ make_collection(
1437
+ "Project",
1438
+ vec![
1439
+ make_field(
1440
+ "_id",
1441
+ FieldType::Primitive {
1442
+ primitive: crate::state_contract::PrimitiveType::String,
1443
+ },
1444
+ ),
1445
+ make_field(
1446
+ "organization",
1447
+ FieldType::Reference {
1448
+ collection: "Organization".into(),
1449
+ },
1450
+ ),
1451
+ ],
1452
+ ),
1453
+ make_collection(
1454
+ "Membership",
1455
+ vec![
1456
+ make_field(
1457
+ "_id",
1458
+ FieldType::Primitive {
1459
+ primitive: crate::state_contract::PrimitiveType::String,
1460
+ },
1461
+ ),
1462
+ make_field(
1463
+ "user",
1464
+ FieldType::Reference {
1465
+ collection: "User".into(),
1466
+ },
1467
+ ),
1468
+ make_field(
1469
+ "organization",
1470
+ FieldType::Reference {
1471
+ collection: "Organization".into(),
1472
+ },
1473
+ ),
1474
+ ],
1475
+ ),
1248
1476
  ]);
1249
1477
 
1250
1478
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1251
- let mut membership = make_stored_row("Membership", "mem1",
1252
- serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"}));
1479
+ let mut membership = make_stored_row(
1480
+ "Membership",
1481
+ "mem1",
1482
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"}),
1483
+ );
1253
1484
  membership.deleted = true; // Mark as deleted
1254
1485
 
1255
1486
  let snapshot = Arc::new(vec![
@@ -1276,24 +1507,67 @@ mod tests {
1276
1507
  use crate::state_contract::FieldType;
1277
1508
 
1278
1509
  let schema = make_schema_with_collections(vec![
1279
- make_collection("Project", vec![
1280
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1281
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1282
- ]),
1283
- make_collection("Membership", vec![
1284
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1285
- make_field("user", FieldType::Reference { collection: "User".into() }),
1286
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1287
- ]),
1510
+ make_collection(
1511
+ "Project",
1512
+ vec![
1513
+ make_field(
1514
+ "_id",
1515
+ FieldType::Primitive {
1516
+ primitive: crate::state_contract::PrimitiveType::String,
1517
+ },
1518
+ ),
1519
+ make_field(
1520
+ "organization",
1521
+ FieldType::Reference {
1522
+ collection: "Organization".into(),
1523
+ },
1524
+ ),
1525
+ ],
1526
+ ),
1527
+ make_collection(
1528
+ "Membership",
1529
+ vec![
1530
+ make_field(
1531
+ "_id",
1532
+ FieldType::Primitive {
1533
+ primitive: crate::state_contract::PrimitiveType::String,
1534
+ },
1535
+ ),
1536
+ make_field(
1537
+ "user",
1538
+ FieldType::Reference {
1539
+ collection: "User".into(),
1540
+ },
1541
+ ),
1542
+ make_field(
1543
+ "organization",
1544
+ FieldType::Reference {
1545
+ collection: "Organization".into(),
1546
+ },
1547
+ ),
1548
+ ],
1549
+ ),
1288
1550
  ]);
1289
1551
 
1290
1552
  let project = serde_json::json!({"_id": "proj1", "organization": "org2"});
1291
1553
 
1292
1554
  let snapshot = Arc::new(vec![
1293
1555
  make_stored_row("Project", "proj1", project),
1294
- make_stored_row("Membership", "mem1", serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"})),
1295
- make_stored_row("Membership", "mem2", serde_json::json!({"_id": "mem2", "user": "alice", "organization": "org2"})),
1296
- make_stored_row("Membership", "mem3", serde_json::json!({"_id": "mem3", "user": "bob", "organization": "org2"})),
1556
+ make_stored_row(
1557
+ "Membership",
1558
+ "mem1",
1559
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"}),
1560
+ ),
1561
+ make_stored_row(
1562
+ "Membership",
1563
+ "mem2",
1564
+ serde_json::json!({"_id": "mem2", "user": "alice", "organization": "org2"}),
1565
+ ),
1566
+ make_stored_row(
1567
+ "Membership",
1568
+ "mem3",
1569
+ serde_json::json!({"_id": "mem3", "user": "bob", "organization": "org2"}),
1570
+ ),
1297
1571
  ]);
1298
1572
 
1299
1573
  let auth_state = AuthorizationState::new(schema, "tenant:123", snapshot);
@@ -1314,21 +1588,51 @@ mod tests {
1314
1588
  use crate::state_contract::FieldType;
1315
1589
 
1316
1590
  let schema = make_schema_with_collections(vec![
1317
- make_collection("Project", vec![
1318
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1319
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1320
- ]),
1321
- make_collection("Membership", vec![
1322
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1323
- make_field("user", FieldType::Reference { collection: "User".into() }),
1324
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1325
- ]),
1591
+ make_collection(
1592
+ "Project",
1593
+ vec![
1594
+ make_field(
1595
+ "_id",
1596
+ FieldType::Primitive {
1597
+ primitive: crate::state_contract::PrimitiveType::String,
1598
+ },
1599
+ ),
1600
+ make_field(
1601
+ "organization",
1602
+ FieldType::Reference {
1603
+ collection: "Organization".into(),
1604
+ },
1605
+ ),
1606
+ ],
1607
+ ),
1608
+ make_collection(
1609
+ "Membership",
1610
+ vec![
1611
+ make_field(
1612
+ "_id",
1613
+ FieldType::Primitive {
1614
+ primitive: crate::state_contract::PrimitiveType::String,
1615
+ },
1616
+ ),
1617
+ make_field(
1618
+ "user",
1619
+ FieldType::Reference {
1620
+ collection: "User".into(),
1621
+ },
1622
+ ),
1623
+ make_field(
1624
+ "organization",
1625
+ FieldType::Reference {
1626
+ collection: "Organization".into(),
1627
+ },
1628
+ ),
1629
+ ],
1630
+ ),
1326
1631
  ]);
1327
1632
 
1328
- let project = serde_json::json!({"_id": "proj1", "organization": "org1", "is_member": "true"});
1329
- let snapshot = Arc::new(vec![
1330
- make_stored_row("Project", "proj1", project),
1331
- ]);
1633
+ let project =
1634
+ serde_json::json!({"_id": "proj1", "organization": "org1", "is_member": "true"});
1635
+ let snapshot = Arc::new(vec![make_stored_row("Project", "proj1", project)]);
1332
1636
 
1333
1637
  let auth_state = AuthorizationState::new(schema, "tenant:123", snapshot);
1334
1638
  let context = RecordAuthorizationContext::new(
@@ -1350,19 +1654,51 @@ mod tests {
1350
1654
  use crate::state_contract::FieldType;
1351
1655
 
1352
1656
  let schema = make_schema_with_collections(vec![
1353
- make_collection("Project", vec![
1354
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1355
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1356
- ]),
1357
- make_collection("Membership", vec![
1358
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1359
- make_field("user", FieldType::Reference { collection: "User".into() }),
1360
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1361
- ]),
1657
+ make_collection(
1658
+ "Project",
1659
+ vec![
1660
+ make_field(
1661
+ "_id",
1662
+ FieldType::Primitive {
1663
+ primitive: crate::state_contract::PrimitiveType::String,
1664
+ },
1665
+ ),
1666
+ make_field(
1667
+ "organization",
1668
+ FieldType::Reference {
1669
+ collection: "Organization".into(),
1670
+ },
1671
+ ),
1672
+ ],
1673
+ ),
1674
+ make_collection(
1675
+ "Membership",
1676
+ vec![
1677
+ make_field(
1678
+ "_id",
1679
+ FieldType::Primitive {
1680
+ primitive: crate::state_contract::PrimitiveType::String,
1681
+ },
1682
+ ),
1683
+ make_field(
1684
+ "user",
1685
+ FieldType::Reference {
1686
+ collection: "User".into(),
1687
+ },
1688
+ ),
1689
+ make_field(
1690
+ "organization",
1691
+ FieldType::Reference {
1692
+ collection: "Organization".into(),
1693
+ },
1694
+ ),
1695
+ ],
1696
+ ),
1362
1697
  ]);
1363
1698
 
1364
1699
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1365
- let membership = serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1700
+ let membership =
1701
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1366
1702
 
1367
1703
  let snapshot = Arc::new(vec![
1368
1704
  make_stored_row("Project", "proj1", project),
@@ -1392,19 +1728,51 @@ mod tests {
1392
1728
  use crate::state_contract::FieldType;
1393
1729
 
1394
1730
  let schema = make_schema_with_collections(vec![
1395
- make_collection("Project", vec![
1396
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1397
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1398
- ]),
1399
- make_collection("Membership", vec![
1400
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1401
- make_field("user_id", FieldType::Reference { collection: "User".into() }), // Variation: user_id not user
1402
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1403
- ]),
1731
+ make_collection(
1732
+ "Project",
1733
+ vec![
1734
+ make_field(
1735
+ "_id",
1736
+ FieldType::Primitive {
1737
+ primitive: crate::state_contract::PrimitiveType::String,
1738
+ },
1739
+ ),
1740
+ make_field(
1741
+ "organization",
1742
+ FieldType::Reference {
1743
+ collection: "Organization".into(),
1744
+ },
1745
+ ),
1746
+ ],
1747
+ ),
1748
+ make_collection(
1749
+ "Membership",
1750
+ vec![
1751
+ make_field(
1752
+ "_id",
1753
+ FieldType::Primitive {
1754
+ primitive: crate::state_contract::PrimitiveType::String,
1755
+ },
1756
+ ),
1757
+ make_field(
1758
+ "user_id",
1759
+ FieldType::Reference {
1760
+ collection: "User".into(),
1761
+ },
1762
+ ), // Variation: user_id not user
1763
+ make_field(
1764
+ "organization",
1765
+ FieldType::Reference {
1766
+ collection: "Organization".into(),
1767
+ },
1768
+ ),
1769
+ ],
1770
+ ),
1404
1771
  ]);
1405
1772
 
1406
1773
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1407
- let membership = serde_json::json!({"_id": "mem1", "user_id": "alice", "organization": "org1"});
1774
+ let membership =
1775
+ serde_json::json!({"_id": "mem1", "user_id": "alice", "organization": "org1"});
1408
1776
 
1409
1777
  let snapshot = Arc::new(vec![
1410
1778
  make_stored_row("Project", "proj1", project),
@@ -1433,21 +1801,50 @@ mod tests {
1433
1801
  use crate::state_contract::FieldType;
1434
1802
 
1435
1803
  let schema = make_schema_with_collections(vec![
1436
- make_collection("Project", vec![
1437
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1438
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1439
- ]),
1440
- make_collection("Membership", vec![
1441
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1442
- make_field("user", FieldType::Reference { collection: "User".into() }),
1443
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1444
- ]),
1804
+ make_collection(
1805
+ "Project",
1806
+ vec![
1807
+ make_field(
1808
+ "_id",
1809
+ FieldType::Primitive {
1810
+ primitive: crate::state_contract::PrimitiveType::String,
1811
+ },
1812
+ ),
1813
+ make_field(
1814
+ "organization",
1815
+ FieldType::Reference {
1816
+ collection: "Organization".into(),
1817
+ },
1818
+ ),
1819
+ ],
1820
+ ),
1821
+ make_collection(
1822
+ "Membership",
1823
+ vec![
1824
+ make_field(
1825
+ "_id",
1826
+ FieldType::Primitive {
1827
+ primitive: crate::state_contract::PrimitiveType::String,
1828
+ },
1829
+ ),
1830
+ make_field(
1831
+ "user",
1832
+ FieldType::Reference {
1833
+ collection: "User".into(),
1834
+ },
1835
+ ),
1836
+ make_field(
1837
+ "organization",
1838
+ FieldType::Reference {
1839
+ collection: "Organization".into(),
1840
+ },
1841
+ ),
1842
+ ],
1843
+ ),
1445
1844
  ]);
1446
1845
 
1447
1846
  let project = serde_json::json!({"_id": "proj1", "organization": "org1", "role": "admin"});
1448
- let snapshot = Arc::new(vec![
1449
- make_stored_row("Project", "proj1", project),
1450
- ]);
1847
+ let snapshot = Arc::new(vec![make_stored_row("Project", "proj1", project)]);
1451
1848
 
1452
1849
  let auth_state = AuthorizationState::new(schema, "tenant:123", snapshot);
1453
1850
  let context = RecordAuthorizationContext::new(
@@ -1469,17 +1866,28 @@ mod tests {
1469
1866
  use crate::state_contract::FieldType;
1470
1867
 
1471
1868
  let schema = make_schema_with_collections(vec![
1472
- make_collection("Project", vec![
1473
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1474
- make_field("organization", FieldType::Reference { collection: "Organization".into() }),
1475
- ]),
1869
+ make_collection(
1870
+ "Project",
1871
+ vec![
1872
+ make_field(
1873
+ "_id",
1874
+ FieldType::Primitive {
1875
+ primitive: crate::state_contract::PrimitiveType::String,
1876
+ },
1877
+ ),
1878
+ make_field(
1879
+ "organization",
1880
+ FieldType::Reference {
1881
+ collection: "Organization".into(),
1882
+ },
1883
+ ),
1884
+ ],
1885
+ ),
1476
1886
  // No membership collection defined
1477
1887
  ]);
1478
1888
 
1479
1889
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1480
- let snapshot = Arc::new(vec![
1481
- make_stored_row("Project", "proj1", project),
1482
- ]);
1890
+ let snapshot = Arc::new(vec![make_stored_row("Project", "proj1", project)]);
1483
1891
 
1484
1892
  let auth_state = AuthorizationState::new(schema, "tenant:123", snapshot);
1485
1893
  let context = RecordAuthorizationContext::new(
@@ -1500,19 +1908,51 @@ mod tests {
1500
1908
  use crate::state_contract::FieldType;
1501
1909
 
1502
1910
  let schema = make_schema_with_collections(vec![
1503
- make_collection("Project", vec![
1504
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1505
- make_field("organization", FieldType::Reference { collection: "Organizations".into() }), // Plural
1506
- ]),
1507
- make_collection("Membership", vec![
1508
- make_field("_id", FieldType::Primitive { primitive: crate::state_contract::PrimitiveType::String }),
1509
- make_field("user", FieldType::Reference { collection: "Users".into() }), // Plural
1510
- make_field("organization", FieldType::Reference { collection: "Organizations".into() }), // Plural
1511
- ]),
1911
+ make_collection(
1912
+ "Project",
1913
+ vec![
1914
+ make_field(
1915
+ "_id",
1916
+ FieldType::Primitive {
1917
+ primitive: crate::state_contract::PrimitiveType::String,
1918
+ },
1919
+ ),
1920
+ make_field(
1921
+ "organization",
1922
+ FieldType::Reference {
1923
+ collection: "Organizations".into(),
1924
+ },
1925
+ ), // Plural
1926
+ ],
1927
+ ),
1928
+ make_collection(
1929
+ "Membership",
1930
+ vec![
1931
+ make_field(
1932
+ "_id",
1933
+ FieldType::Primitive {
1934
+ primitive: crate::state_contract::PrimitiveType::String,
1935
+ },
1936
+ ),
1937
+ make_field(
1938
+ "user",
1939
+ FieldType::Reference {
1940
+ collection: "Users".into(),
1941
+ },
1942
+ ), // Plural
1943
+ make_field(
1944
+ "organization",
1945
+ FieldType::Reference {
1946
+ collection: "Organizations".into(),
1947
+ },
1948
+ ), // Plural
1949
+ ],
1950
+ ),
1512
1951
  ]);
1513
1952
 
1514
1953
  let project = serde_json::json!({"_id": "proj1", "organization": "org1"});
1515
- let membership = serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1954
+ let membership =
1955
+ serde_json::json!({"_id": "mem1", "user": "alice", "organization": "org1"});
1516
1956
 
1517
1957
  let snapshot = Arc::new(vec![
1518
1958
  make_stored_row("Project", "proj1", project),
@@ -1543,7 +1983,9 @@ mod tests {
1543
1983
  );
1544
1984
 
1545
1985
  let result = PolicyEvaluator::evaluate_record(
1546
- PolicySubject::Self_ { field: "user".to_string() },
1986
+ PolicySubject::Self_ {
1987
+ field: "user".to_string(),
1988
+ },
1547
1989
  &context,
1548
1990
  );
1549
1991
  assert_eq!(result, Ok(AuthorizationDecision::Allow));
@@ -1559,7 +2001,9 @@ mod tests {
1559
2001
  );
1560
2002
 
1561
2003
  let result = PolicyEvaluator::evaluate_record(
1562
- PolicySubject::Self_ { field: "user".to_string() },
2004
+ PolicySubject::Self_ {
2005
+ field: "user".to_string(),
2006
+ },
1563
2007
  &context,
1564
2008
  );
1565
2009
  assert!(result.is_err());
@@ -1576,7 +2020,9 @@ mod tests {
1576
2020
  );
1577
2021
 
1578
2022
  let result = PolicyEvaluator::evaluate_record(
1579
- PolicySubject::Self_ { field: "user".to_string() },
2023
+ PolicySubject::Self_ {
2024
+ field: "user".to_string(),
2025
+ },
1580
2026
  &context,
1581
2027
  );
1582
2028
  assert!(result.is_err());
@@ -1593,7 +2039,9 @@ mod tests {
1593
2039
  );
1594
2040
 
1595
2041
  let result = PolicyEvaluator::evaluate_record(
1596
- PolicySubject::Self_ { field: "user".to_string() },
2042
+ PolicySubject::Self_ {
2043
+ field: "user".to_string(),
2044
+ },
1597
2045
  &context,
1598
2046
  );
1599
2047
  assert!(result.is_err());
@@ -1610,7 +2058,9 @@ mod tests {
1610
2058
  );
1611
2059
 
1612
2060
  let result = PolicyEvaluator::evaluate_record(
1613
- PolicySubject::Self_ { field: "user".to_string() },
2061
+ PolicySubject::Self_ {
2062
+ field: "user".to_string(),
2063
+ },
1614
2064
  &context,
1615
2065
  );
1616
2066
  assert!(result.is_err());
@@ -1627,7 +2077,9 @@ mod tests {
1627
2077
  );
1628
2078
 
1629
2079
  let result = PolicyEvaluator::evaluate_record(
1630
- PolicySubject::Self_ { field: "owner".to_string() },
2080
+ PolicySubject::Self_ {
2081
+ field: "owner".to_string(),
2082
+ },
1631
2083
  &context,
1632
2084
  );
1633
2085
  assert_eq!(result, Ok(AuthorizationDecision::Allow));
@@ -1643,7 +2095,9 @@ mod tests {
1643
2095
  );
1644
2096
 
1645
2097
  let result = PolicyEvaluator::evaluate_record(
1646
- PolicySubject::Self_ { field: "owner".to_string() },
2098
+ PolicySubject::Self_ {
2099
+ field: "owner".to_string(),
2100
+ },
1647
2101
  &context,
1648
2102
  );
1649
2103
  assert!(result.is_err());
@@ -1663,7 +2117,9 @@ mod tests {
1663
2117
 
1664
2118
  #[test]
1665
2119
  fn policy_subject_as_str_for_self() {
1666
- let subject = PolicySubject::Self_ { field: "user".to_string() };
2120
+ let subject = PolicySubject::Self_ {
2121
+ field: "user".to_string(),
2122
+ };
1667
2123
  assert_eq!(subject.as_str(), "self");
1668
2124
  }
1669
2125
  }