1use std::collections::{HashMap, HashSet};
7use std::sync::Arc;
8
9use crate::api::{RedDBError, RedDBResult};
10use crate::storage::RedDB;
11use reddb_types::Value;
12
13thread_local! {
14 static CURRENT_CONN_ID: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
18
19 static CURRENT_AUTH_IDENTITY: std::cell::RefCell<Option<(String, crate::auth::Role)>> =
27 const { std::cell::RefCell::new(None) };
28
29 static CURRENT_AUTH_PRINCIPAL: std::cell::RefCell<Option<crate::auth::UserId>> =
32 const { std::cell::RefCell::new(None) };
33
34 static CURRENT_SNAPSHOT: std::cell::RefCell<Option<SnapshotContext>> =
44 const { std::cell::RefCell::new(None) };
45
46 static HAS_SNAPSHOT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
52
53 static CURRENT_TENANT_ID: std::cell::RefCell<Option<String>> =
63 const { std::cell::RefCell::new(None) };
64
65 static CURRENT_CONFIG_RESOLVER: std::cell::RefCell<Option<ConfigResolver>> =
69 const { std::cell::RefCell::new(None) };
70
71 static CURRENT_SECRET_RESOLVER: std::cell::RefCell<Option<SecretResolver>> =
75 const { std::cell::RefCell::new(None) };
76
77 static CURRENT_KV_RESOLVER: std::cell::RefCell<Option<KvResolver>> =
81 const { std::cell::RefCell::new(None) };
82}
83
84pub use reddb_engine::transaction::read::SnapshotContext;
85use reddb_engine::transaction::read::{record_serializable_read, visibility_check};
86
87pub fn set_current_connection_id(id: u64) {
96 CURRENT_CONN_ID.with(|c| c.set(id));
97}
98
99pub fn clear_current_connection_id() {
101 CURRENT_CONN_ID.with(|c| c.set(0));
102}
103
104pub fn current_connection_id() -> u64 {
107 CURRENT_CONN_ID.with(|c| c.get())
108}
109
110pub fn set_current_auth_identity(username: String, role: crate::auth::Role) {
114 set_current_auth_principal(crate::auth::UserId::platform(username), role);
115}
116
117pub fn set_current_auth_principal(principal: crate::auth::UserId, role: crate::auth::Role) {
119 CURRENT_AUTH_IDENTITY.with(|cell| {
120 *cell.borrow_mut() = Some((principal.username.clone(), role));
121 });
122 CURRENT_AUTH_PRINCIPAL.with(|cell| *cell.borrow_mut() = Some(principal));
123}
124
125pub(crate) fn set_current_auth_projection(username: String, role: crate::auth::Role) {
128 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = Some((username, role)));
129}
130
131pub fn clear_current_auth_identity() {
135 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = None);
136 CURRENT_AUTH_PRINCIPAL.with(|cell| *cell.borrow_mut() = None);
137}
138
139pub(crate) fn current_auth_identity() -> Option<(String, crate::auth::Role)> {
142 CURRENT_AUTH_IDENTITY.with(|cell| cell.borrow().clone())
143}
144
145pub(crate) fn current_auth_principal() -> Option<crate::auth::UserId> {
147 CURRENT_AUTH_PRINCIPAL.with(|cell| cell.borrow().clone())
148}
149
150pub fn current_auth_identity_for_audit() -> Option<(String, crate::auth::Role)> {
154 current_auth_identity()
155}
156
157pub fn set_current_tenant(tenant_id: String) {
162 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = Some(tenant_id));
163}
164
165pub fn clear_current_tenant() {
168 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = None);
169}
170
171pub fn current_tenant() -> Option<String> {
182 let inherited = CURRENT_TENANT_ID.with(|cell| cell.borrow().clone());
183 if let Some(over) = current_scope_override() {
184 if over.tenant.is_active() {
185 return over.tenant.resolve(inherited);
186 }
187 }
188 if let Some(tx_local) = current_tx_local_tenant() {
189 return tx_local;
190 }
191 inherited
192}
193
194thread_local! {
195 static TX_LOCAL_TENANT: std::cell::RefCell<Option<Option<String>>> =
204 const { std::cell::RefCell::new(None) };
205}
206
207fn current_tx_local_tenant() -> Option<Option<String>> {
208 TX_LOCAL_TENANT.with(|cell| cell.borrow().clone())
209}
210
211pub(crate) fn parse_set_local_tenant(query: &str) -> RedDBResult<Option<Option<String>>> {
217 let mut tokens = query.split_ascii_whitespace();
218 let Some(w1) = tokens.next() else {
219 return Ok(None);
220 };
221 if !w1.eq_ignore_ascii_case("SET") {
222 return Ok(None);
223 }
224 let Some(w2) = tokens.next() else {
225 return Ok(None);
226 };
227 if !w2.eq_ignore_ascii_case("LOCAL") {
228 return Ok(None);
229 }
230 let Some(w3) = tokens.next() else {
231 return Ok(None);
232 };
233 if !w3.eq_ignore_ascii_case("TENANT") {
234 return Ok(None);
235 }
236 let rest: String = tokens.collect::<Vec<_>>().join(" ");
237 let rest = rest.trim().trim_end_matches(';').trim();
238 let value_str = rest.strip_prefix('=').map(|s| s.trim()).unwrap_or(rest);
239 if value_str.is_empty() {
240 return Err(RedDBError::Query(
241 "SET LOCAL TENANT expects a string literal or NULL".to_string(),
242 ));
243 }
244 if value_str.eq_ignore_ascii_case("NULL") {
245 return Ok(Some(None));
246 }
247 if value_str.starts_with('\'') && value_str.ends_with('\'') && value_str.len() >= 2 {
248 let inner = &value_str[1..value_str.len() - 1];
249 return Ok(Some(Some(inner.to_string())));
250 }
251 Err(RedDBError::Query(format!(
252 "SET LOCAL TENANT expects a string literal or NULL, got `{value_str}`"
253 )))
254}
255
256pub(crate) struct TxLocalTenantGuard;
257
258impl TxLocalTenantGuard {
259 pub fn install(value: Option<Option<String>>) -> Self {
260 TX_LOCAL_TENANT.with(|cell| *cell.borrow_mut() = value);
261 Self
262 }
263}
264
265impl Drop for TxLocalTenantGuard {
266 fn drop(&mut self) {
267 TX_LOCAL_TENANT.with(|cell| *cell.borrow_mut() = None);
268 }
269}
270
271thread_local! {
272 static SCOPE_OVERRIDES: std::cell::RefCell<Vec<crate::runtime::within_clause::ScopeOverride>> =
279 const { std::cell::RefCell::new(Vec::new()) };
280}
281
282pub(crate) fn push_scope_override(over: crate::runtime::within_clause::ScopeOverride) {
283 SCOPE_OVERRIDES.with(|cell| cell.borrow_mut().push(over));
284}
285
286pub(crate) fn pop_scope_override() {
287 SCOPE_OVERRIDES.with(|cell| {
288 cell.borrow_mut().pop();
289 });
290}
291
292pub(crate) fn current_scope_override() -> Option<crate::runtime::within_clause::ScopeOverride> {
293 SCOPE_OVERRIDES.with(|cell| cell.borrow().last().cloned())
294}
295
296pub(crate) fn has_scope_override_active() -> bool {
300 SCOPE_OVERRIDES.with(|cell| !cell.borrow().is_empty())
301}
302
303pub(crate) struct ScopeOverrideGuard;
307
308impl ScopeOverrideGuard {
309 pub fn install(over: crate::runtime::within_clause::ScopeOverride) -> Self {
310 push_scope_override(over);
311 Self
312 }
313}
314
315impl Drop for ScopeOverrideGuard {
316 fn drop(&mut self) {
317 pop_scope_override();
318 }
319}
320
321pub(crate) fn current_user_projected() -> Option<String> {
327 let inherited = current_auth_identity().map(|(u, _)| u);
328 if let Some(over) = current_scope_override() {
329 if over.user.is_active() {
330 return over.user.resolve(inherited);
331 }
332 }
333 inherited
334}
335
336pub(crate) fn current_role_projected() -> Option<String> {
337 let inherited = current_auth_identity().map(|(_, r)| format!("{r:?}").to_lowercase());
338 if let Some(over) = current_scope_override() {
339 if over.role.is_active() {
340 return over.role.resolve(inherited);
341 }
342 }
343 inherited
344}
345
346pub(crate) fn current_secret_value(path: &str) -> Option<Value> {
347 try_current_secret_value(path).ok().flatten()
348}
349
350pub(crate) fn try_current_secret_value(path: &str) -> RedDBResult<Option<Value>> {
351 let key = path.to_ascii_lowercase();
352 CURRENT_SECRET_RESOLVER.with(|cell| {
353 let mut resolver = cell.borrow_mut();
354 let Some(resolver) = resolver.as_mut() else {
355 return Ok(None);
356 };
357 if let Some(value) = resolver.values.get(&key) {
358 return Ok(value.clone());
359 }
360 let value = resolver.runtime.query_secret_value(&key)?;
361 resolver.values.insert(key, value.clone());
362 Ok(value)
363 })
364}
365
366pub(crate) fn secret_query_input(value: Value) -> Option<Value> {
369 if !matches!(value, Value::Secret(_)) {
370 return Some(value);
371 }
372 CURRENT_SECRET_RESOLVER.with(|cell| {
373 let resolver = cell.borrow();
374 resolver.as_ref()?.runtime.secret_query_input(value)
375 })
376}
377
378pub(crate) fn try_secret_query_input(
379 value: Value,
380) -> Result<Option<Value>, super::impl_dml_crypto::SecretInputReadLimit> {
381 if !matches!(value, Value::Secret(_)) {
382 return Ok(Some(value));
383 }
384 CURRENT_SECRET_RESOLVER.with(|cell| {
385 let resolver = cell.borrow();
386 match resolver.as_ref() {
387 Some(resolver) => resolver.runtime.try_secret_query_input(value),
388 None => Ok(None),
389 }
390 })
391}
392
393pub(crate) fn secret_query_output(value: Value) -> crate::RedDBResult<Value> {
394 if value.is_null() || matches!(value, Value::Secret(_)) {
395 return Ok(value);
396 }
397 CURRENT_SECRET_RESOLVER.with(|cell| {
398 let resolver = cell.borrow();
399 let resolver = resolver.as_ref().ok_or_else(|| {
400 RedDBError::Query("secret expression requires an active statement".into())
401 })?;
402 resolver.runtime.secret_query_output(value)
403 })
404}
405
406#[cfg(test)]
407fn secret_value_from_snapshot(values: &HashMap<String, String>, key: &str) -> Option<String> {
408 if key.starts_with("red.secret.") {
409 return None;
410 }
411 values.get(key).cloned()
412}
413
414struct SecretResolver {
415 runtime: super::RedDBRuntime,
416 values: HashMap<String, Option<Value>>,
417}
418
419pub(crate) struct SecretStoreGuard {
420 previous: Option<SecretResolver>,
421}
422
423impl SecretStoreGuard {
424 pub(super) fn install(runtime: &super::RedDBRuntime) -> Self {
425 let previous = CURRENT_SECRET_RESOLVER.with(|cell| {
426 cell.replace(Some(SecretResolver {
427 runtime: runtime.clone(),
428 values: HashMap::new(),
429 }))
430 });
431 Self { previous }
432 }
433}
434
435impl Drop for SecretStoreGuard {
436 fn drop(&mut self) {
437 let previous = self.previous.take();
438 CURRENT_SECRET_RESOLVER.with(|cell| {
439 cell.replace(previous);
440 });
441 }
442}
443
444pub(crate) fn current_kv_value(path: &str) -> Option<String> {
452 let key = path.to_ascii_lowercase();
453 CURRENT_KV_RESOLVER.with(|cell| {
454 let mut resolver = cell.borrow_mut();
455 let resolver = resolver.as_mut()?;
456 if resolver.values.is_none() {
457 resolver.values = resolver
458 .store
459 .as_ref()
460 .map(|store| store.plain_kv_snapshot());
461 }
462 let values = resolver.values.as_ref()?;
463 let found = values
467 .get(&key)
468 .map(|value| (key.as_str(), value))
469 .or_else(|| {
470 key.strip_prefix("red.kv/")
471 .and_then(|rest| values.get(rest).map(|value| (rest, value)))
472 })?;
473 if !resolver.can_read(found.0) {
474 return None;
475 }
476 Some(found.1.clone())
477 })
478}
479
480struct KvResolver {
481 store: Option<Arc<crate::auth::store::AuthStore>>,
482 values: Option<HashMap<String, String>>,
483 identity: Option<(String, crate::auth::Role, Option<String>)>,
484}
485
486impl KvResolver {
487 fn can_read(&self, key: &str) -> bool {
488 let Some(store) = &self.store else {
489 return true;
490 };
491 let Some((username, role, tenant)) = &self.identity else {
492 return true;
493 };
494 let principal = crate::auth::UserId::from_parts(tenant.as_deref(), username);
495 let mut resource =
496 crate::auth::policies::ResourceRef::new("kv".to_string(), key.to_string());
497 if let Some(tenant) = tenant {
498 resource = resource.with_tenant(tenant.clone());
499 }
500 let ctx = crate::auth::policies::EvalContext {
501 principal_tenant: tenant.clone(),
502 current_tenant: tenant.clone(),
503 peer_ip: None,
504 mfa_present: false,
505 now_ms: crate::auth::now_ms(),
506 principal_is_admin_role: *role == crate::auth::Role::Admin,
507 principal_is_platform_scoped: tenant.is_none(),
508 };
509 store.check_policy_authz_with_role(&principal, "kv:read", &resource, &ctx, *role)
510 }
511}
512
513pub(crate) struct KvStoreGuard {
514 previous: Option<Box<KvResolver>>,
519}
520
521impl KvStoreGuard {
522 pub(super) fn install(store: Option<Arc<crate::auth::store::AuthStore>>) -> Self {
523 let previous = CURRENT_KV_RESOLVER.with(|cell| {
524 cell.replace(Some(KvResolver {
525 store,
526 values: None,
527 identity: current_auth_identity().map(|(username, role)| {
528 let tenant = current_tenant();
529 (username, role, tenant)
530 }),
531 }))
532 });
533 Self {
534 previous: previous.map(Box::new),
535 }
536 }
537}
538
539impl Drop for KvStoreGuard {
540 fn drop(&mut self) {
541 let previous = self.previous.take().map(|b| *b);
542 CURRENT_KV_RESOLVER.with(|cell| {
543 cell.replace(previous);
544 });
545 }
546}
547
548pub(crate) fn update_current_kv_value(path: &str, value: Option<String>) {
551 let key = path.to_ascii_lowercase();
552 CURRENT_KV_RESOLVER.with(|cell| {
553 if let Some(resolver) = cell.borrow_mut().as_mut() {
554 let Some(values) = resolver.values.as_mut() else {
555 return;
556 };
557 match value {
558 Some(value) => {
559 values.insert(key, value);
560 }
561 None => {
562 values.remove(&key);
563 }
564 }
565 }
566 });
567}
568
569pub(crate) fn current_config_value(path: &str) -> Option<Value> {
570 try_current_config_value(path).ok().flatten()
571}
572
573pub(crate) fn try_current_config_value(path: &str) -> RedDBResult<Option<Value>> {
574 let key = path.to_ascii_lowercase();
575 CURRENT_CONFIG_RESOLVER.with(|cell| {
576 let mut resolver = cell.borrow_mut();
577 let Some(resolver) = resolver.as_mut() else {
578 return Ok(None);
579 };
580 if !resolver.can_read(&key) {
581 return Ok(None);
582 }
583 if resolver.values.is_none() {
584 resolver.values = Some(latest_config_snapshot(&resolver.db)?);
585 }
586 let found = {
592 let values = resolver
593 .values
594 .as_ref()
595 .expect("invariant: successful snapshot is installed");
596 values
597 .get(&key)
598 .map(|value| (key.clone(), value.clone()))
599 .or_else(|| {
600 key.strip_prefix("red.config/").and_then(|rest| {
601 values
602 .get(rest)
603 .map(|value| (rest.to_string(), value.clone()))
604 })
605 })
606 };
607 let Some(found) = found else { return Ok(None) };
608 if !resolver.can_read(&found.0) {
612 return Ok(None);
613 }
614 Ok(Some(found.1))
615 })
616}
617
618pub(crate) fn update_current_config_value(path: &str, value: Value) {
619 let key = path.to_ascii_lowercase();
620 CURRENT_CONFIG_RESOLVER.with(|cell| {
621 if let Some(resolver) = cell.borrow_mut().as_mut() {
622 if let Some(values) = resolver.values.as_mut() {
623 values.insert(key, value);
624 }
625 }
626 });
627}
628
629pub(crate) fn update_current_secret_value(_path: &str, _value: Option<String>) {
630 CURRENT_SECRET_RESOLVER.with(|cell| {
631 if let Some(resolver) = cell.borrow_mut().as_mut() {
632 resolver.values.clear();
633 }
634 });
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640
641 #[test]
648 fn snapshot_thread_local_stays_private_to_the_statement_frame() {
649 let src = include_str!("execution_context.rs");
650
651 for name in ["CURRENT_SNAPSHOT", "HAS_SNAPSHOT"] {
652 assert!(
653 !src.contains(&format!("pub static {name}"))
654 && !src.contains(&format!("pub(crate) static {name}")),
655 "{name} must stay private to execution_context; expose an explicit \
656 Option<SnapshotContext> via capture_current_snapshot instead"
657 );
658 }
659
660 assert!(
661 src.contains("the only sanctioned ambient read"),
662 "capture_current_snapshot must keep documenting that it is the single \
663 sanctioned ambient read of the snapshot thread-local"
664 );
665 }
666
667 #[test]
668 fn secret_snapshot_hard_blocks_system_keys() {
669 let values = HashMap::from([
670 ("red.secret.aes_key".into(), "internal".into()),
671 ("acme.key".into(), "user-value".into()),
672 ]);
673 assert_eq!(
674 secret_value_from_snapshot(&values, "red.secret.aes_key"),
675 None
676 );
677 assert_eq!(
678 secret_value_from_snapshot(&values, "acme.key").as_deref(),
679 Some("user-value")
680 );
681 }
682
683 #[test]
684 fn config_snapshot_does_not_copy_superseded_values() {
685 use crate::storage::no_malloc_hot_paths::measure_allocations;
686
687 let observe = |payload_bytes| {
691 let values: Vec<_> = (1..=64)
692 .rev()
693 .map(|id| (id, Value::Blob(vec![id as u8; payload_bytes])))
694 .collect();
695 let (latest, allocations) = measure_allocations(|| {
696 let mut latest = HashMap::new();
697 for (id, value) in &values {
698 let mut shared = None;
699 insert_latest_config_value(
700 &mut latest,
701 "fixture.copy".into(),
702 *id,
703 value,
704 &mut shared,
705 );
706 insert_latest_config_value(
707 &mut latest,
708 "red.config/fixture.copy".into(),
709 *id,
710 value,
711 &mut shared,
712 );
713 }
714 latest
715 });
716 assert_eq!(latest["fixture.copy"].0, 64);
717 assert_eq!(
718 *latest["fixture.copy"].1,
719 Value::Blob(vec![64; payload_bytes])
720 );
721 assert!(Arc::ptr_eq(
722 &latest["fixture.copy"].1,
723 &latest["red.config/fixture.copy"].1
724 ));
725 allocations
726 };
727 let empty = observe(0);
728 let large = observe(64 * 1024);
729 assert!(
730 large.allocated_bytes <= empty.allocated_bytes + 2 * 64 * 1024,
731 "superseded config payload copies: {empty:?} -> {large:?}"
732 );
733 }
734
735 #[test]
736 fn config_snapshot_keeps_bare_and_namespaced_aliases_for_canonical_keys() {
737 use crate::storage::{EntityData, EntityId, RowData, UnifiedEntity};
738 let runtime = super::super::RedDBRuntime::in_memory().expect("runtime");
739 let store = runtime.db().store();
740 let manager = store.get_or_create_collection("red_config");
741 assert!(Arc::ptr_eq(
742 &manager,
743 &store.get_collection("red.config").unwrap()
744 ));
745 for (id, key, value, tombstone) in [
746 (1, "theme", "dark", false),
747 (2, "theme", "light", false),
748 (3, "other", "hidden", true),
749 ] {
750 let mut entity =
751 UnifiedEntity::table_row(EntityId::new(2_000_000 + id), "red_config", id, vec![]);
752 entity.data = EntityData::Row(RowData {
753 columns: vec![],
754 schema: None,
755 named: Some(HashMap::from([
756 ("key".into(), Value::text(key)),
757 ("value".into(), Value::text(value)),
758 ("tombstone".into(), Value::Boolean(tombstone)),
759 ])),
760 });
761 manager.insert(entity).expect("config row");
762 }
763 let values = latest_config_snapshot(runtime.db().as_ref()).expect("snapshot");
764 assert_eq!(values.get("theme"), Some(&Value::text("light")));
765 assert_eq!(values.get("red.config/theme"), Some(&Value::text("light")));
766 assert!(!values.contains_key("red.config.theme"));
767 assert_eq!(values.get("other"), Some(&Value::text("hidden")));
768 assert!(!values.contains_key("red.config/other"));
769 }
770
771 fn with_kv_values<T>(values: HashMap<String, String>, f: impl FnOnce() -> T) -> T {
772 CURRENT_KV_RESOLVER.with(|cell| {
773 cell.replace(Some(KvResolver {
774 store: None,
775 values: Some(values),
776 identity: None,
777 }));
778 });
779 let result = f();
780 CURRENT_KV_RESOLVER.with(|cell| {
781 cell.replace(None);
782 });
783 result
784 }
785
786 #[test]
787 fn current_kv_value_resolves_namespaced_and_bare_keys() {
788 let values = HashMap::from([("acme.key".to_string(), "plain-value".to_string())]);
789
790 with_kv_values(values, || {
791 assert_eq!(
793 current_kv_value("red.kv/acme.key").as_deref(),
794 Some("plain-value")
795 );
796 assert_eq!(current_kv_value("acme.key").as_deref(), Some("plain-value"));
798 assert_eq!(current_kv_value("red.kv/missing.key"), None);
800 });
801 }
802
803 #[test]
804 fn config_key_read_allowed_hard_blocks_reserved_namespace() {
805 assert!(!config_key_read_allowed(
808 None,
809 None,
810 "red.config/red.config.aes_key"
811 ));
812 assert!(!config_key_read_allowed(None, None, "red.config.aes_key"));
813 assert!(config_key_read_allowed(
815 None,
816 None,
817 "red.config/ai.openrouter.default.key"
818 ));
819 assert!(config_key_read_allowed(None, None, "acme.flags.beta"));
820 }
821
822 #[test]
823 fn is_config_collection_matches_system_config_stores() {
824 assert!(is_config_collection("red_config"));
825 assert!(is_config_collection("RED_CONFIG"));
826 assert!(is_config_collection("red.config"));
827 assert!(!is_config_collection("users"));
828 assert!(!is_config_collection("red_kv"));
829 }
830
831 #[test]
832 fn update_current_kv_value_reflects_in_resolver() {
833 with_kv_values(HashMap::new(), || {
834 assert_eq!(current_kv_value("red.kv/feature.flag"), None);
835 update_current_kv_value("feature.flag", Some("on".to_string()));
836 assert_eq!(
837 current_kv_value("red.kv/feature.flag").as_deref(),
838 Some("on")
839 );
840 update_current_kv_value("feature.flag", None);
841 assert_eq!(current_kv_value("red.kv/feature.flag"), None);
842 });
843 }
844}
845
846fn latest_config_snapshot(db: &RedDB) -> RedDBResult<HashMap<String, Value>> {
847 let mut latest: HashMap<String, (u64, Arc<Value>)> = HashMap::new();
848
849 if let Some(manager) = db.store().get_collection("red.config") {
852 super::function_budget::try_scan(
853 |visit| manager.try_for_each_entity(usize::MAX, visit),
854 |entity| {
855 let Some(row) = entity.data.as_row() else {
856 return true;
857 };
858 let Some(Value::Text(key)) = row.get_field("key") else {
859 return true;
860 };
861 let value = row.get_field("value").unwrap_or(&Value::Null);
862 let id = entity.id.raw();
863 let key = key.to_ascii_lowercase();
864 let mut shared = None;
867 insert_latest_config_value(&mut latest, key.clone(), id, value, &mut shared);
868 if !matches!(row.get_field("tombstone"), Some(Value::Boolean(true))) {
869 insert_latest_config_value(
870 &mut latest,
871 format!("red.config/{key}"),
872 id,
873 value,
874 &mut shared,
875 );
876 }
877 true
878 },
879 )?;
880 }
881
882 Ok(latest
883 .into_iter()
884 .map(|(key, (_, value))| {
885 (
886 key,
887 Arc::try_unwrap(value).unwrap_or_else(|shared| (*shared).clone()),
888 )
889 })
890 .collect())
891}
892
893fn insert_latest_config_value(
894 latest: &mut HashMap<String, (u64, Arc<Value>)>,
895 key: String,
896 id: u64,
897 value: &Value,
898 shared: &mut Option<Arc<Value>>,
899) {
900 match latest.get(&key) {
901 Some((prev_id, _)) if *prev_id > id => {}
902 _ => {
903 let shared = shared.get_or_insert_with(|| Arc::new(value.clone()));
904 latest.insert(key, (id, Arc::clone(shared)));
905 }
906 }
907}
908
909struct ConfigResolver {
910 db: Arc<RedDB>,
911 store: Option<Arc<crate::auth::store::AuthStore>>,
912 identity: Option<(String, crate::auth::Role, Option<String>)>,
913 values: Option<HashMap<String, Value>>,
914}
915
916impl ConfigResolver {
917 fn can_read(&self, key: &str) -> bool {
918 config_key_read_allowed(self.store.as_ref(), self.identity.as_ref(), key)
919 }
920}
921
922fn is_reserved_config_key(key: &str) -> bool {
926 let key = key.strip_prefix("red.config/").unwrap_or(key);
927 key.starts_with("red.config.")
928 || key == "red.config"
929 || key == super::function_catalog::REGISTRY_KEY
930}
931
932fn is_config_collection(collection: &str) -> bool {
936 let normalized = collection.to_ascii_lowercase();
937 reddb_engine::storage::system_collection_names::canonical_or_self(&normalized) == "red.config"
938}
939
940fn config_resource_target(key: &str) -> String {
941 let bare = key.strip_prefix("red.config/").unwrap_or(key);
942 format!("red.config/{bare}")
943}
944
945fn config_key_read_allowed(
951 store: Option<&Arc<crate::auth::store::AuthStore>>,
952 identity: Option<&(String, crate::auth::Role, Option<String>)>,
953 key: &str,
954) -> bool {
955 if is_reserved_config_key(key) {
956 return false;
957 }
958 let Some(store) = store else {
959 return true;
960 };
961 if !store.iam_authorization_enabled() {
962 return true;
963 }
964 let Some((username, role, tenant)) = identity else {
965 return true;
966 };
967 let principal = crate::auth::UserId::from_parts(tenant.as_deref(), username);
968 let mut resource =
969 crate::auth::policies::ResourceRef::new("config".to_string(), config_resource_target(key));
970 if let Some(tenant) = tenant {
971 resource = resource.with_tenant(tenant.clone());
972 }
973 let ctx = crate::auth::policies::EvalContext {
974 principal_tenant: tenant.clone(),
975 current_tenant: tenant.clone(),
976 peer_ip: None,
977 mfa_present: false,
978 now_ms: crate::auth::now_ms(),
979 principal_is_admin_role: *role == crate::auth::Role::Admin,
980 principal_is_platform_scoped: tenant.is_none(),
981 };
982 store.check_policy_authz_with_role(&principal, "config:read", &resource, &ctx, *role)
983}
984
985pub(crate) fn config_read_permitted(key: &str) -> bool {
989 let key = key.to_ascii_lowercase();
990 CURRENT_CONFIG_RESOLVER.with(|cell| match cell.borrow().as_ref() {
991 Some(resolver) => resolver.can_read(&key),
992 None => !is_reserved_config_key(&key),
993 })
994}
995
996pub(crate) fn kv_read_permitted(collection: &str, key: &str) -> bool {
999 if is_config_collection(collection) {
1000 config_read_permitted(key)
1001 } else {
1002 true
1003 }
1004}
1005
1006pub(crate) struct ConfigSnapshotGuard {
1007 previous: Option<ConfigResolver>,
1008}
1009
1010impl ConfigSnapshotGuard {
1011 pub(super) fn install(
1012 db: Arc<RedDB>,
1013 store: Option<Arc<crate::auth::store::AuthStore>>,
1014 ) -> Self {
1015 let identity = current_auth_identity().map(|(username, role)| {
1016 let tenant = current_tenant();
1017 (username, role, tenant)
1018 });
1019 let previous = CURRENT_CONFIG_RESOLVER.with(|cell| {
1020 cell.replace(Some(ConfigResolver {
1021 db,
1022 store,
1023 identity,
1024 values: None,
1025 }))
1026 });
1027 Self { previous }
1028 }
1029}
1030
1031impl Drop for ConfigSnapshotGuard {
1032 fn drop(&mut self) {
1033 let previous = self.previous.take();
1034 CURRENT_CONFIG_RESOLVER.with(|cell| {
1035 cell.replace(previous);
1036 });
1037 }
1038}
1039
1040pub fn set_current_snapshot(ctx: SnapshotContext) {
1045 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = Some(ctx));
1046 HAS_SNAPSHOT.with(|c| c.set(true));
1047}
1048
1049pub fn clear_current_snapshot() {
1050 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = None);
1051 HAS_SNAPSHOT.with(|c| c.set(false));
1052}
1053
1054pub(crate) struct CurrentSnapshotGuard {
1060 previous: Option<SnapshotContext>,
1061}
1062
1063impl CurrentSnapshotGuard {
1064 pub(crate) fn install(ctx: SnapshotContext) -> Self {
1065 let previous = CURRENT_SNAPSHOT.with(|cell| cell.borrow().clone());
1066 set_current_snapshot(ctx);
1067 Self { previous }
1068 }
1069}
1070
1071impl Drop for CurrentSnapshotGuard {
1072 fn drop(&mut self) {
1073 let prev = self.previous.take();
1074 let has = prev.is_some();
1075 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = prev);
1076 HAS_SNAPSHOT.with(|c| c.set(has));
1077 }
1078}
1079
1080#[inline]
1091pub fn entity_visible_under_current_snapshot(
1092 entity: &crate::storage::unified::entity::UnifiedEntity,
1093) -> bool {
1094 if crate::runtime::ai::moderation::entity_moderation_hidden(entity) {
1101 return false;
1102 }
1103 if !HAS_SNAPSHOT.with(|c| c.get()) {
1109 return entity.xmax == 0;
1110 }
1111 CURRENT_SNAPSHOT.with(|cell| {
1112 let guard = cell.borrow();
1113 let Some(ctx) = guard.as_ref() else {
1114 return true;
1115 };
1116 let visible = reddb_engine::transaction::read::entity_visibility_check(ctx, entity);
1117 if visible {
1118 record_serializable_read(ctx, entity);
1119 }
1120 visible
1121 })
1122}
1123
1124#[inline]
1129pub(crate) fn xids_visible_under_current_snapshot(xmin: u64, xmax: u64) -> bool {
1130 if !HAS_SNAPSHOT.with(|c| c.get()) {
1131 return true;
1132 }
1133 CURRENT_SNAPSHOT.with(|cell| {
1134 let guard = cell.borrow();
1135 let Some(ctx) = guard.as_ref() else {
1136 return true;
1137 };
1138 visibility_check(ctx, xmin, xmax)
1139 })
1140}
1141
1142pub fn capture_current_snapshot() -> Option<SnapshotContext> {
1167 CURRENT_SNAPSHOT.with(|cell| cell.borrow().clone())
1168}
1169
1170pub(crate) fn current_snapshot_requires_index_fallback() -> bool {
1175 if !HAS_SNAPSHOT.with(|c| c.get()) {
1176 return false;
1177 }
1178 CURRENT_SNAPSHOT.with(|cell| {
1179 cell.borrow()
1180 .as_ref()
1181 .is_some_and(|ctx| ctx.requires_index_fallback)
1182 })
1183}
1184
1185#[derive(Clone, Default)]
1200pub struct SnapshotBundle {
1201 pub snapshot: Option<SnapshotContext>,
1202 pub auth: Option<(String, crate::auth::Role)>,
1203 pub auth_principal: Option<crate::auth::UserId>,
1204 pub tenant: Option<String>,
1205}
1206
1207pub fn snapshot_bundle() -> SnapshotBundle {
1210 SnapshotBundle {
1211 snapshot: capture_current_snapshot(),
1212 auth: current_auth_identity(),
1213 auth_principal: current_auth_principal(),
1214 tenant: CURRENT_TENANT_ID.with(|cell| cell.borrow().clone()),
1215 }
1216}
1217
1218pub fn with_snapshot_bundle<R>(bundle: &SnapshotBundle, f: impl FnOnce() -> R) -> R {
1223 struct Guard {
1224 prev_snapshot: Option<SnapshotContext>,
1225 prev_auth: Option<(String, crate::auth::Role)>,
1226 prev_auth_principal: Option<crate::auth::UserId>,
1227 prev_tenant: Option<String>,
1228 }
1229 impl Drop for Guard {
1230 fn drop(&mut self) {
1231 let snap = self.prev_snapshot.take();
1232 let has = snap.is_some();
1233 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = snap);
1234 HAS_SNAPSHOT.with(|c| c.set(has));
1235 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = self.prev_auth.take());
1236 CURRENT_AUTH_PRINCIPAL
1237 .with(|cell| *cell.borrow_mut() = self.prev_auth_principal.take());
1238 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = self.prev_tenant.take());
1239 }
1240 }
1241
1242 let _guard = {
1243 let prev_snapshot = CURRENT_SNAPSHOT.with(|cell| cell.borrow().clone());
1244 let prev_auth = CURRENT_AUTH_IDENTITY.with(|cell| cell.borrow().clone());
1245 let prev_auth_principal = CURRENT_AUTH_PRINCIPAL.with(|cell| cell.borrow().clone());
1246 let prev_tenant = CURRENT_TENANT_ID.with(|cell| cell.borrow().clone());
1247
1248 match bundle.snapshot.clone() {
1249 Some(ctx) => set_current_snapshot(ctx),
1250 None => clear_current_snapshot(),
1251 }
1252 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = bundle.auth.clone());
1253 CURRENT_AUTH_PRINCIPAL.with(|cell| *cell.borrow_mut() = bundle.auth_principal.clone());
1254 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = bundle.tenant.clone());
1255
1256 Guard {
1257 prev_snapshot,
1258 prev_auth,
1259 prev_auth_principal,
1260 prev_tenant,
1261 }
1262 };
1263 f()
1264}
1265
1266#[inline]
1270pub fn entity_visible_with_context(
1271 ctx: Option<&SnapshotContext>,
1272 entity: &crate::storage::unified::entity::UnifiedEntity,
1273) -> bool {
1274 reddb_engine::storage::visibility::entity_visible_with_context(ctx, entity)
1275}