pub mod channel;
pub mod owner;
pub mod tenant;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScopeRegistryCounts {
pub tenant: usize,
pub owner: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeRegistryState {
Uninitialized,
Initialized,
PolicyOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScopeModeConflict {
pub current: ScopeRegistryState,
pub requested: ScopeRegistryState,
}
impl std::fmt::Display for ScopeModeConflict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"RLS isolation mode is sealed as {:?}; refusing transition to {:?}",
self.current, self.requested
)
}
}
impl std::error::Error for ScopeModeConflict {}
#[derive(Debug)]
pub(crate) struct ScopeModeCoordinator {
state: std::sync::atomic::AtomicU8,
policy_only_reason: std::sync::OnceLock<&'static str>,
}
const MODE_UNINITIALIZED: u8 = 0;
const MODE_INITIALIZED: u8 = 1;
const MODE_POLICY_ONLY: u8 = 2;
impl ScopeModeCoordinator {
pub(crate) const fn new() -> Self {
Self {
state: std::sync::atomic::AtomicU8::new(MODE_UNINITIALIZED),
policy_only_reason: std::sync::OnceLock::new(),
}
}
fn decode(raw: u8) -> ScopeRegistryState {
match raw {
MODE_INITIALIZED => ScopeRegistryState::Initialized,
MODE_POLICY_ONLY => ScopeRegistryState::PolicyOnly,
_ => ScopeRegistryState::Uninitialized,
}
}
pub(crate) fn state(&self) -> ScopeRegistryState {
Self::decode(self.state.load(std::sync::atomic::Ordering::Acquire))
}
fn seal(&self, target: u8) -> Result<(), ScopeModeConflict> {
match self.state.compare_exchange(
MODE_UNINITIALIZED,
target,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
) {
Ok(_) => Ok(()),
Err(current) if current == target => Ok(()),
Err(current) => Err(ScopeModeConflict {
current: Self::decode(current),
requested: Self::decode(target),
}),
}
}
pub(crate) fn declare_initialized(&self) -> Result<(), ScopeModeConflict> {
self.seal(MODE_INITIALIZED)
}
pub(crate) fn declare_policy_only(
&self,
reason: &'static str,
) -> Result<(), ScopeModeConflict> {
self.seal(MODE_POLICY_ONLY)?;
self.policy_only_reason.get_or_init(|| reason);
Ok(())
}
pub(crate) fn policy_only_reason(&self) -> Option<&'static str> {
if self.state() == ScopeRegistryState::PolicyOnly {
self.policy_only_reason.get().copied()
} else {
None
}
}
}
impl Default for ScopeModeCoordinator {
fn default() -> Self {
Self::new()
}
}
static SCOPE_MODE: ScopeModeCoordinator = ScopeModeCoordinator::new();
pub fn scope_registry_state() -> ScopeRegistryState {
SCOPE_MODE.state()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ScopeInitError {
ModeConflict(ScopeModeConflict),
NoScopedTables,
RegistryUnavailable(String),
}
impl std::fmt::Display for ScopeInitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ModeConflict(conflict) => write!(f, "{conflict}"),
Self::NoScopedTables => write!(
f,
"refusing to seal RLS scope registries: no tenant- or owner-scoped tables were registered (declare_no_scoped_tables(reason) if that is intentional)"
),
Self::RegistryUnavailable(why) => {
write!(f, "RLS scope registry unavailable: {why}")
}
}
}
}
impl std::error::Error for ScopeInitError {}
impl From<ScopeModeConflict> for ScopeInitError {
fn from(conflict: ScopeModeConflict) -> Self {
Self::ModeConflict(conflict)
}
}
fn seal_initialized_if_populated(
counts: ScopeRegistryCounts,
) -> Result<ScopeRegistryCounts, ScopeInitError> {
let live = live_registered_total()?;
seal_initialized_if_populated_with(live, counts)
}
fn live_registered_total() -> Result<usize, ScopeInitError> {
let tenant = tenant::try_tenant_table_count().map_err(ScopeInitError::RegistryUnavailable)?;
let owner = owner::try_owner_table_count().map_err(ScopeInitError::RegistryUnavailable)?;
Ok(tenant + owner)
}
fn seal_initialized_if_populated_with(
live_registered: usize,
counts: ScopeRegistryCounts,
) -> Result<ScopeRegistryCounts, ScopeInitError> {
if live_registered == 0 {
return Err(ScopeInitError::NoScopedTables);
}
SCOPE_MODE.declare_initialized()?;
Ok(counts)
}
pub fn init_scope_registries(
schema: &crate::migrate::Schema,
) -> Result<ScopeRegistryCounts, ScopeInitError> {
if SCOPE_MODE.state() == ScopeRegistryState::PolicyOnly {
return Err(ScopeModeConflict {
current: ScopeRegistryState::PolicyOnly,
requested: ScopeRegistryState::Initialized,
}
.into());
}
let tenant = tenant::register_from_migrate_schema(schema)
.map_err(ScopeInitError::RegistryUnavailable)?;
let owner =
owner::register_from_migrate_schema(schema).map_err(ScopeInitError::RegistryUnavailable)?;
seal_initialized_if_populated(ScopeRegistryCounts { tenant, owner })
}
pub fn init_scope_registries_from_tables(
tenant_tables: &[(&str, &str)],
owner_tables: &[(&str, &str)],
) -> Result<ScopeRegistryCounts, ScopeInitError> {
if SCOPE_MODE.state() == ScopeRegistryState::PolicyOnly {
return Err(ScopeModeConflict {
current: ScopeRegistryState::PolicyOnly,
requested: ScopeRegistryState::Initialized,
}
.into());
}
let tenant = tenant::try_register_tenant_tables(tenant_tables)
.map_err(ScopeInitError::RegistryUnavailable)?;
let owner = owner::try_register_owner_tables(owner_tables)
.map_err(ScopeInitError::RegistryUnavailable)?;
seal_initialized_if_populated(ScopeRegistryCounts { tenant, owner })
}
pub fn declare_no_scoped_tables(reason: &'static str) -> Result<(), ScopeInitError> {
if live_registered_total()? != 0 {
return Err(ScopeInitError::RegistryUnavailable(
"registries are not empty; use init_scope_registries instead".to_string(),
));
}
SCOPE_MODE.declare_initialized()?;
NO_SCOPED_TABLES_REASON.get_or_init(|| reason);
Ok(())
}
static NO_SCOPED_TABLES_REASON: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
pub fn no_scoped_tables_reason() -> Option<&'static str> {
NO_SCOPED_TABLES_REASON.get().copied()
}
pub fn declare_policy_only_isolation(reason: &'static str) -> Result<(), ScopeModeConflict> {
SCOPE_MODE.declare_policy_only(reason)
}
pub fn policy_only_reason() -> Option<&'static str> {
SCOPE_MODE.policy_only_reason()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SuperAdminToken {
_private: (),
}
impl SuperAdminToken {
pub fn for_system_process(_reason: &str) -> Self {
Self { _private: () }
}
pub fn for_webhook(_source: &str) -> Self {
Self { _private: () }
}
pub fn for_auth(_operation: &str) -> Self {
Self { _private: () }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RlsContext {
pub tenant_id: String,
is_super_admin: bool,
is_global: bool,
user_id: String,
}
impl RlsContext {
pub fn tenant(tenant_id: &str) -> Self {
Self {
tenant_id: tenant_id.to_string(),
is_super_admin: false,
is_global: false,
user_id: String::new(),
}
}
pub fn global() -> Self {
Self {
tenant_id: String::new(),
is_super_admin: false,
is_global: true,
user_id: String::new(),
}
}
pub fn super_admin(_token: SuperAdminToken) -> Self {
let nil = "00000000-0000-0000-0000-000000000000".to_string();
Self {
tenant_id: nil,
is_super_admin: true,
is_global: false,
user_id: String::new(),
}
}
pub fn empty() -> Self {
Self {
tenant_id: String::new(),
is_super_admin: false,
is_global: false,
user_id: String::new(),
}
}
pub fn user(user_id: &str) -> Self {
Self {
tenant_id: String::new(),
is_super_admin: false,
is_global: false,
user_id: user_id.to_string(),
}
}
pub fn with_user(mut self, user_id: &str) -> Self {
self.user_id = user_id.to_string();
self
}
pub fn has_tenant(&self) -> bool {
!self.tenant_id.is_empty()
}
pub fn has_user(&self) -> bool {
!self.user_id.is_empty()
}
pub fn user_id(&self) -> &str {
&self.user_id
}
pub fn bypasses_rls(&self) -> bool {
self.is_super_admin
}
pub fn is_global(&self) -> bool {
self.is_global
}
}
impl std::fmt::Display for RlsContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_super_admin {
write!(f, "RlsContext(super_admin)")
} else if self.is_global {
write!(f, "RlsContext(global)")
} else if !self.tenant_id.is_empty() {
write!(f, "RlsContext(tenant={})", self.tenant_id)
} else {
write!(f, "RlsContext(none)")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scope_mode_seals_one_way_policy_only_first() {
let mode = ScopeModeCoordinator::new();
assert_eq!(mode.state(), ScopeRegistryState::Uninitialized);
assert_eq!(mode.policy_only_reason(), None);
mode.declare_policy_only("db policies only").unwrap();
assert_eq!(mode.state(), ScopeRegistryState::PolicyOnly);
assert_eq!(mode.policy_only_reason(), Some("db policies only"));
mode.declare_policy_only("second reason").unwrap();
assert_eq!(mode.policy_only_reason(), Some("db policies only"));
let err = mode.declare_initialized().unwrap_err();
assert_eq!(err.current, ScopeRegistryState::PolicyOnly);
assert_eq!(err.requested, ScopeRegistryState::Initialized);
assert_eq!(mode.state(), ScopeRegistryState::PolicyOnly);
}
#[test]
fn scope_mode_seals_one_way_initialized_first() {
let mode = ScopeModeCoordinator::new();
mode.declare_initialized().unwrap();
mode.declare_initialized().unwrap();
assert_eq!(mode.state(), ScopeRegistryState::Initialized);
let err = mode.declare_policy_only("too late").unwrap_err();
assert_eq!(err.current, ScopeRegistryState::Initialized);
assert_eq!(mode.state(), ScopeRegistryState::Initialized);
assert_eq!(
mode.policy_only_reason(),
None,
"a refused declaration must not record a reason"
);
}
#[test]
fn low_level_registration_is_mode_neutral_and_empty_init_refuses_to_seal() {
let before = scope_registry_state();
tenant::try_register_tenant_tables(&[]).unwrap();
owner::try_register_owner_tables(&[]).unwrap();
assert_eq!(
scope_registry_state(),
before,
"empty low-level registration must not change the mode"
);
tenant::try_register_tenant_tables(&[("_mode_neutral_probe", "tenant_id")]).unwrap();
assert_eq!(
scope_registry_state(),
before,
"non-empty low-level registration must not change the mode either"
);
assert!(
tenant::try_tenant_table_count().unwrap() > 0,
"…but the table IS recorded"
);
}
#[test]
fn init_from_tables_refuses_empty_then_seals_on_real_registration() {
assert_eq!(
seal_initialized_if_populated_with(
0,
ScopeRegistryCounts {
tenant: 0,
owner: 0
}
),
Err(ScopeInitError::NoScopedTables)
);
let counts =
init_scope_registries_from_tables(&[("_init_from_tables_t", "tenant_id")], &[])
.expect("one real table seals Initialized");
assert_eq!(
counts,
ScopeRegistryCounts {
tenant: 1,
owner: 0
}
);
assert_eq!(scope_registry_state(), ScopeRegistryState::Initialized);
}
#[test]
fn init_from_migrate_schema_reports_registry_failure_instead_of_zero() {
let schema = crate::migrate::parse_qail(
"table _init_schema_orders {\n id UUID primary_key\n tenant_id UUID\n}\n",
)
.unwrap();
assert_eq!(tenant::register_from_migrate_schema(&schema), Ok(1));
assert_eq!(owner::register_from_migrate_schema(&schema), Ok(0));
}
fn poison<T: Send + Sync + 'static>(lock: &'static std::sync::RwLock<T>) {
let result = std::thread::spawn(move || {
let _guard = lock.write().unwrap();
panic!("poison the registry lock");
})
.join();
assert!(result.is_err(), "writer thread must have panicked");
assert!(lock.is_poisoned());
}
#[test]
fn poisoned_tenant_registry_is_an_error_for_count_and_registration() {
let lock: &'static std::sync::RwLock<tenant::TenantRegistry> = Box::leak(Box::new(
std::sync::RwLock::new(tenant::TenantRegistry::new()),
));
tenant::register_into(lock, &[("_poison_orders", "tenant_id")]).unwrap();
poison(lock);
let count = tenant::count_in(lock).expect_err("poisoned count must not read as 0");
assert!(count.contains("poisoned"), "{count}");
let reg = tenant::register_into(lock, &[("_poison_more", "tenant_id")])
.expect_err("poisoned registration must not be silently discarded");
assert!(reg.contains("poisoned"), "{reg}");
}
#[test]
fn poisoned_owner_registry_is_an_error_for_count_and_registration() {
let lock: &'static std::sync::RwLock<owner::OwnerRegistry> = Box::leak(Box::new(
std::sync::RwLock::new(owner::OwnerRegistry::new()),
));
owner::register_into(lock, &[("_poison_listings", "seller_id")]).unwrap();
poison(lock);
assert!(
owner::count_in(lock)
.expect_err("poisoned count must not read as 0")
.contains("poisoned")
);
assert!(
owner::register_into(lock, &[("_poison_more", "seller_id")])
.expect_err("poisoned registration must not be silently discarded")
.contains("poisoned")
);
}
#[test]
fn poisoned_registry_lookup_is_an_error_not_unregistered() {
let tenant_lock: &'static std::sync::RwLock<tenant::TenantRegistry> = Box::leak(Box::new(
std::sync::RwLock::new(tenant::TenantRegistry::new()),
));
tenant::register_into(tenant_lock, &[("_poison_lookup_orders", "tenant_id")]).unwrap();
assert_eq!(
tenant::lookup_in(tenant_lock, "_poison_lookup_orders"),
Ok(Some("tenant_id".to_string()))
);
poison(tenant_lock);
let err = tenant::lookup_in(tenant_lock, "_poison_lookup_orders")
.expect_err("a registered table behind a poisoned lock must NOT read as None");
assert!(err.contains("poisoned"), "{err}");
let owner_lock: &'static std::sync::RwLock<owner::OwnerRegistry> = Box::leak(Box::new(
std::sync::RwLock::new(owner::OwnerRegistry::new()),
));
owner::register_into(owner_lock, &[("_poison_lookup_listings", "seller_id")]).unwrap();
poison(owner_lock);
assert!(
owner::lookup_in(owner_lock, "_poison_lookup_listings")
.expect_err("poisoned owner lookup must error")
.contains("poisoned")
);
}
#[test]
fn compatibility_lookup_collapses_error_but_scoping_does_not_use_it() {
let lock: &'static std::sync::RwLock<tenant::TenantRegistry> = Box::leak(Box::new(
std::sync::RwLock::new(tenant::TenantRegistry::new()),
));
tenant::register_into(lock, &[("_compat_orders", "tenant_id")]).unwrap();
poison(lock);
assert_eq!(
tenant::lookup_in(lock, "_compat_orders").ok().flatten(),
None
);
assert!(tenant::lookup_in(lock, "_compat_orders").is_err());
}
#[test]
fn registry_unavailable_never_seals_initialized() {
let mode = ScopeModeCoordinator::new();
let live: Result<usize, ScopeInitError> = Err(ScopeInitError::RegistryUnavailable(
"owner registry lock poisoned".into(),
));
let outcome = live.and_then(|n| {
if n == 0 {
Err(ScopeInitError::NoScopedTables)
} else {
mode.declare_initialized().map_err(Into::into)
}
});
assert!(matches!(
outcome,
Err(ScopeInitError::RegistryUnavailable(_))
));
assert_eq!(mode.state(), ScopeRegistryState::Uninitialized);
}
#[test]
fn test_tenant_context() {
let ctx = RlsContext::tenant("t-123");
assert_eq!(ctx.tenant_id, "t-123");
assert!(!ctx.bypasses_rls());
assert!(ctx.has_tenant());
}
#[test]
fn test_super_admin_via_named_constructors() {
let token = SuperAdminToken::for_system_process("test");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
let token = SuperAdminToken::for_webhook("test");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
let token = SuperAdminToken::for_auth("test");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_display() {
let token = SuperAdminToken::for_system_process("test_display");
assert_eq!(
RlsContext::super_admin(token).to_string(),
"RlsContext(super_admin)"
);
assert_eq!(RlsContext::tenant("x").to_string(), "RlsContext(tenant=x)");
}
#[test]
fn test_equality() {
let a = RlsContext::tenant("t-1");
let b = RlsContext::tenant("t-1");
let c = RlsContext::tenant("t-2");
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_empty_context() {
let ctx = RlsContext::empty();
assert!(!ctx.has_tenant());
assert!(!ctx.bypasses_rls());
assert!(!ctx.is_global());
}
#[test]
fn test_global_context() {
let ctx = RlsContext::global();
assert!(!ctx.has_tenant());
assert!(!ctx.bypasses_rls());
assert!(ctx.is_global());
assert_eq!(ctx.to_string(), "RlsContext(global)");
}
#[test]
fn test_for_system_process() {
let token = SuperAdminToken::for_system_process("cron::check_expired_holds");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_for_webhook() {
let token = SuperAdminToken::for_webhook("xendit_callback");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_for_auth() {
let token = SuperAdminToken::for_auth("login");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_all_constructors_produce_equal_tokens() {
let a = SuperAdminToken::for_system_process("a");
let b = SuperAdminToken::for_webhook("b");
let c = SuperAdminToken::for_auth("c");
assert_eq!(a, b);
assert_eq!(b, c);
}
#[test]
fn test_user_context() {
let ctx = RlsContext::user("550e8400-e29b-41d4-a716-446655440000");
assert!(!ctx.has_tenant());
assert!(!ctx.bypasses_rls());
assert!(!ctx.is_global());
assert!(ctx.has_user());
assert_eq!(ctx.user_id(), "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn test_with_user_preserves_tenant_scope() {
let ctx = RlsContext::tenant("tenant-1").with_user("user-1");
assert_eq!(ctx.tenant_id, "tenant-1");
assert_eq!(ctx.user_id(), "user-1");
assert!(ctx.has_tenant());
assert!(ctx.has_user());
assert!(!ctx.bypasses_rls());
}
#[test]
fn test_user_context_display() {
let ctx = RlsContext::user("u-123");
assert_eq!(ctx.to_string(), "RlsContext(none)");
}
#[test]
fn test_other_constructors_have_no_user() {
assert!(!RlsContext::tenant("t-1").has_user());
assert!(!RlsContext::global().has_user());
assert!(!RlsContext::empty().has_user());
let token = SuperAdminToken::for_auth("test");
assert!(!RlsContext::super_admin(token).has_user());
}
}