pub mod governance;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EnterpriseMode {
Disabled,
Optional,
Required,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeAuthMode {
#[default]
LocalSingleTenant,
HostedSingleTenant,
EnterpriseRequired,
}
impl RuntimeAuthMode {
pub fn as_str(self) -> &'static str {
match self {
Self::LocalSingleTenant => "local_single_tenant",
Self::HostedSingleTenant => "hosted_single_tenant",
Self::EnterpriseRequired => "enterprise_required",
}
}
pub fn parse(value: &str) -> Result<Self, ParseRuntimeAuthModeError> {
value.parse()
}
}
impl core::str::FromStr for RuntimeAuthMode {
type Err = ParseRuntimeAuthModeError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.trim().to_ascii_lowercase().as_str() {
""
| "local"
| "local_single_tenant"
| "local-single-tenant"
| "single_tenant"
| "single-tenant" => Ok(Self::LocalSingleTenant),
"hosted" | "hosted_single_tenant" | "hosted-single-tenant" => {
Ok(Self::HostedSingleTenant)
}
"enterprise" | "enterprise_required" | "enterprise-required" | "required" => {
Ok(Self::EnterpriseRequired)
}
_ => Err(ParseRuntimeAuthModeError),
}
}
}
impl core::fmt::Display for RuntimeAuthMode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParseRuntimeAuthModeError;
impl core::fmt::Display for ParseRuntimeAuthModeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("invalid runtime auth mode")
}
}
impl std::error::Error for ParseRuntimeAuthModeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EnterpriseBridgeState {
Absent,
Noop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EnterpriseCapability {
Status,
TenantContext,
NoopBridge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum TenantSource {
#[default]
LocalImplicit,
Explicit,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestPrincipal {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actor_id: Option<String>,
#[serde(default)]
pub source: String,
}
impl RequestPrincipal {
pub fn anonymous() -> Self {
Self {
actor_id: None,
source: "anonymous".to_string(),
}
}
pub fn authenticated_user(actor_id: impl Into<String>, source: impl Into<String>) -> Self {
Self {
actor_id: Some(actor_id.into()),
source: source.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AutomationPrincipal {
pub automation_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub owner_id: Option<String>,
#[serde(default)]
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ExecutionPrincipal {
Request(RequestPrincipal),
Automation(AutomationPrincipal),
ServiceAccount {
service_account_id: String,
},
#[default]
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthorityChain {
pub initiated_by: RequestPrincipal,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub owned_by: Option<AutomationPrincipal>,
pub executed_as: ExecutionPrincipal,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approved_by: Option<RequestPrincipal>,
}
impl AuthorityChain {
pub fn from_request(principal: RequestPrincipal) -> Self {
Self {
initiated_by: principal.clone(),
owned_by: None,
executed_as: ExecutionPrincipal::Request(principal),
approved_by: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HumanActor {
pub actor_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issuer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
impl HumanActor {
pub fn tandem_user(actor_id: impl Into<String>) -> Self {
Self {
actor_id: actor_id.into(),
provider: Some("tandem".to_string()),
issuer: None,
subject: None,
email: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResourceKind {
Organization,
Workspace,
OrganizationUnit,
Department,
Group,
Project,
DataRoom,
SharedDrive,
DocumentCollection,
DataStore,
Dataset,
Document,
Repository,
Directory,
File,
Artifact,
MemorySpace,
KnowledgeSpace,
SecretProviderCredential,
Automation,
Run,
Approval,
AuditExport,
McpServer,
McpTool,
ConnectorInstance,
SourceBinding,
SourceObject,
IngestionJob,
ExternalIntegrationAccount,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourcePathSegment {
pub kind: ResourceKind,
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl ResourcePathSegment {
pub fn new(kind: ResourceKind, id: impl Into<String>) -> Self {
Self {
kind,
id: id.into(),
name: None,
}
}
pub fn named(kind: ResourceKind, id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
kind,
id: id.into(),
name: Some(name.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceRef {
pub organization_id: String,
pub workspace_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
pub resource_kind: ResourceKind,
pub resource_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub parent_path: Vec<ResourcePathSegment>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub branch_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_prefix: Option<String>,
}
impl ResourceRef {
pub fn new(
organization_id: impl Into<String>,
workspace_id: impl Into<String>,
resource_kind: ResourceKind,
resource_id: impl Into<String>,
) -> Self {
Self {
organization_id: organization_id.into(),
workspace_id: workspace_id.into(),
project_id: None,
resource_kind,
resource_id: resource_id.into(),
parent_path: Vec::new(),
branch_id: None,
path_prefix: None,
}
}
pub fn with_project_id(mut self, project_id: impl Into<String>) -> Self {
self.project_id = Some(project_id.into());
self
}
pub fn with_parent_path(mut self, parent_path: Vec<ResourcePathSegment>) -> Self {
self.parent_path = parent_path;
self
}
pub fn with_branch_id(mut self, branch_id: impl Into<String>) -> Self {
self.branch_id = Some(branch_id.into());
self
}
pub fn with_path_prefix(mut self, path_prefix: impl Into<String>) -> Self {
self.path_prefix = Some(path_prefix.into());
self
}
pub fn applies_to(&self, target: &ResourceRef) -> bool {
if self.organization_id != target.organization_id {
return false;
}
if self.workspace_id != "*" && self.workspace_id != target.workspace_id {
return false;
}
match self.resource_kind {
ResourceKind::Organization => self.resource_id == target.organization_id,
ResourceKind::Workspace | ResourceKind::Department => {
self.resource_id == target.workspace_id
|| self.resource_id == "*"
|| self.resource_id == target.resource_id
}
ResourceKind::Project => {
target.project_id.as_deref() == Some(self.resource_id.as_str())
|| target.resource_id == self.resource_id
}
_ => self.matches_resource_or_path(target),
}
}
fn matches_resource_or_path(&self, target: &ResourceRef) -> bool {
if self.project_id.is_some() && self.project_id != target.project_id {
return false;
}
if self.resource_kind == target.resource_kind && self.resource_id == target.resource_id {
return self.path_prefix_applies_to(target);
}
self.path_prefix
.as_deref()
.zip(target.path_prefix.as_deref())
.map(|(prefix, target_prefix)| target_prefix.starts_with(prefix))
.unwrap_or(false)
}
fn path_prefix_applies_to(&self, target: &ResourceRef) -> bool {
match (self.path_prefix.as_deref(), target.path_prefix.as_deref()) {
(Some(prefix), Some(target_prefix)) => target_prefix.starts_with(prefix),
(Some(_), None) => false,
(None, _) => true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceScope {
pub root: ResourceRef,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_resources: Vec<ResourceRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub denied_resources: Vec<ResourceRef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_depth: Option<u32>,
}
impl ResourceScope {
pub fn root(root: ResourceRef) -> Self {
Self {
root,
allowed_resources: Vec::new(),
denied_resources: Vec::new(),
max_depth: None,
}
}
pub fn explicitly_denies(&self, resource: &ResourceRef) -> bool {
self.denied_resources
.iter()
.any(|denied| denied.applies_to(resource))
}
pub fn contains(&self, resource: &ResourceRef) -> bool {
!self.explicitly_denies(resource)
&& (self.root.applies_to(resource)
|| self
.allowed_resources
.iter()
.any(|allowed| allowed.applies_to(resource)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccessPermission {
View,
Read,
Edit,
Execute,
Delegate,
Admin,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DataClass {
Public,
Internal,
Confidential,
Restricted,
Executive,
Credential,
Regulated,
CustomerData,
SourceCode,
FinancialRecord,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PrincipalKind {
HumanUser,
OrganizationUnit,
Group,
Department,
AgentWorker,
Automation,
ServiceAccount,
ExternalDelegate,
SupportOperator,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PrincipalRef {
pub kind: PrincipalKind,
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant_actor_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issuer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
}
impl PrincipalRef {
pub fn new(kind: PrincipalKind, id: impl Into<String>) -> Self {
Self {
kind,
id: id.into(),
tenant_actor_id: None,
issuer: None,
subject: None,
}
}
pub fn human_user(id: impl Into<String>) -> Self {
Self::new(PrincipalKind::HumanUser, id)
}
pub fn agent_worker(id: impl Into<String>) -> Self {
Self::new(PrincipalKind::AgentWorker, id)
}
pub fn organization_unit(id: impl Into<String>) -> Self {
Self::new(PrincipalKind::OrganizationUnit, id)
}
pub fn with_tenant_actor_id(mut self, tenant_actor_id: impl Into<String>) -> Self {
self.tenant_actor_id = Some(tenant_actor_id.into());
self
}
pub fn with_issuer_subject(
mut self,
issuer: impl Into<String>,
subject: impl Into<String>,
) -> Self {
self.issuer = Some(issuer.into());
self.subject = Some(subject.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GrantSource {
Direct,
OrganizationUnitMembership,
GroupMembership,
DepartmentMembership,
Inherited,
ExecutiveGlobal,
Delegation,
BreakGlass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum AccessEffect {
#[default]
Allow,
Deny,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScopedGrant {
pub grant_id: String,
pub principal: PrincipalRef,
pub resource: ResourceRef,
#[serde(default)]
pub effect: AccessEffect,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub permissions: Vec<AccessPermission>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub data_classes: Vec<DataClass>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_patterns: Vec<String>,
pub grant_source: GrantSource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_principal: Option<PrincipalRef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub delegation_id: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OrganizationUnitKind {
Department,
Team,
RoleDomain,
ContractorGroup,
ExecutiveGroup,
ClinicalGroup,
OperationalGroup,
Custom,
#[default]
Unspecified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OrganizationUnitState {
#[default]
Active,
Disabled,
}
impl OrganizationUnitState {
pub fn is_active(self) -> bool {
matches!(self, Self::Active)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrganizationUnit {
pub unit_id: String,
pub tenant_context: TenantContext,
#[serde(default = "default_taxonomy_id")]
pub taxonomy_id: String,
pub display_name: String,
#[serde(default)]
pub kind: OrganizationUnitKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_unit_id: Option<String>,
#[serde(default)]
pub state: OrganizationUnitState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub labels: Vec<String>,
pub created_by: PrincipalRef,
pub created_at_ms: u64,
pub updated_at_ms: u64,
}
impl OrganizationUnit {
pub fn active(
unit_id: impl Into<String>,
tenant_context: TenantContext,
display_name: impl Into<String>,
kind: OrganizationUnitKind,
created_by: PrincipalRef,
now_ms: u64,
) -> Self {
Self {
unit_id: unit_id.into(),
tenant_context,
taxonomy_id: default_taxonomy_id(),
display_name: display_name.into(),
kind,
parent_unit_id: None,
state: OrganizationUnitState::Active,
description: None,
labels: Vec::new(),
created_by,
created_at_ms: now_ms,
updated_at_ms: now_ms,
}
}
pub fn with_parent_unit_id(mut self, parent_unit_id: impl Into<String>) -> Self {
self.parent_unit_id = Some(parent_unit_id.into());
self
}
pub fn with_taxonomy_id(mut self, taxonomy_id: impl Into<String>) -> Self {
self.taxonomy_id = taxonomy_id.into();
self
}
pub fn with_state(mut self, state: OrganizationUnitState, updated_at_ms: u64) -> Self {
self.state = state;
self.updated_at_ms = updated_at_ms;
self
}
pub fn principal_ref(&self) -> PrincipalRef {
PrincipalRef::organization_unit(format!("{}/{}", self.taxonomy_id, self.unit_id))
}
pub fn resource_ref(&self) -> ResourceRef {
ResourceRef::new(
self.tenant_context.org_id.clone(),
self.tenant_context.workspace_id.clone(),
ResourceKind::OrganizationUnit,
format!("{}/{}", self.taxonomy_id, self.unit_id),
)
}
}
fn default_taxonomy_id() -> String {
"organization_unit".to_string()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OrganizationUnitMembershipSource {
#[default]
Direct,
HostedControlPlane,
Scim,
GoogleWorkspace,
Okta,
ManualImport,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrganizationUnitMembership {
pub membership_id: String,
pub tenant_context: TenantContext,
pub unit: PrincipalRef,
pub member: PrincipalRef,
#[serde(default)]
pub source: OrganizationUnitMembershipSource,
#[serde(default)]
pub state: OrganizationUnitState,
pub created_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at_ms: Option<u64>,
}
impl OrganizationUnitMembership {
pub fn active(
membership_id: impl Into<String>,
tenant_context: TenantContext,
unit: PrincipalRef,
member: PrincipalRef,
source: OrganizationUnitMembershipSource,
created_at_ms: u64,
) -> Self {
Self {
membership_id: membership_id.into(),
tenant_context,
unit,
member,
source,
state: OrganizationUnitState::Active,
created_at_ms,
expires_at_ms: None,
}
}
pub fn with_expires_at_ms(mut self, expires_at_ms: u64) -> Self {
self.expires_at_ms = Some(expires_at_ms);
self
}
pub fn is_active_at(&self, now_ms: u64) -> bool {
self.state.is_active()
&& self
.expires_at_ms
.map(|expires_at_ms| expires_at_ms > now_ms)
.unwrap_or(true)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrganizationUnitAccessGrant {
pub grant_id: String,
pub tenant_context: TenantContext,
pub unit: PrincipalRef,
pub resource: ResourceRef,
#[serde(default)]
pub effect: AccessEffect,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub permissions: Vec<AccessPermission>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub data_classes: Vec<DataClass>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_patterns: Vec<String>,
#[serde(default)]
pub state: OrganizationUnitState,
pub created_at_ms: u64,
pub updated_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at_ms: Option<u64>,
}
impl OrganizationUnitAccessGrant {
pub fn active(
grant_id: impl Into<String>,
tenant_context: TenantContext,
unit: PrincipalRef,
resource: ResourceRef,
created_at_ms: u64,
) -> Self {
Self {
grant_id: grant_id.into(),
tenant_context,
unit,
resource,
effect: AccessEffect::Allow,
permissions: Vec::new(),
data_classes: Vec::new(),
tool_patterns: Vec::new(),
state: OrganizationUnitState::Active,
created_at_ms,
updated_at_ms: created_at_ms,
expires_at_ms: None,
}
}
pub fn with_effect(mut self, effect: AccessEffect) -> Self {
self.effect = effect;
self
}
pub fn with_permissions(mut self, permissions: Vec<AccessPermission>) -> Self {
self.permissions = permissions;
self
}
pub fn with_data_classes(mut self, data_classes: Vec<DataClass>) -> Self {
self.data_classes = data_classes;
self
}
pub fn with_tool_patterns(mut self, tool_patterns: Vec<String>) -> Self {
self.tool_patterns = tool_patterns;
self
}
pub fn with_expires_at_ms(mut self, expires_at_ms: u64) -> Self {
self.expires_at_ms = Some(expires_at_ms);
self
}
pub fn is_active_at(&self, now_ms: u64) -> bool {
self.state.is_active()
&& self
.expires_at_ms
.map(|expires_at_ms| expires_at_ms > now_ms)
.unwrap_or(true)
}
pub fn to_scoped_grant_for_membership(
&self,
membership: &OrganizationUnitMembership,
now_ms: u64,
) -> Option<ScopedGrant> {
if self.tenant_context.org_id != membership.tenant_context.org_id
|| self.tenant_context.workspace_id != membership.tenant_context.workspace_id
|| self.tenant_context.deployment_id != membership.tenant_context.deployment_id
|| self.unit != membership.unit
|| !self.is_active_at(now_ms)
|| !membership.is_active_at(now_ms)
{
return None;
}
let mut grant = ScopedGrant::new(
format!("{}::{}", membership.membership_id, self.grant_id),
membership.member.clone(),
self.resource.clone(),
GrantSource::OrganizationUnitMembership,
)
.with_effect(self.effect)
.with_permissions(self.permissions.clone())
.with_data_classes(self.data_classes.clone())
.with_tool_patterns(self.tool_patterns.clone())
.with_source_principal(self.unit.clone());
grant.expires_at_ms = match (membership.expires_at_ms, self.expires_at_ms) {
(Some(left), Some(right)) => Some(left.min(right)),
(Some(value), None) | (None, Some(value)) => Some(value),
(None, None) => None,
};
Some(grant)
}
}
impl ScopedGrant {
pub fn new(
grant_id: impl Into<String>,
principal: PrincipalRef,
resource: ResourceRef,
grant_source: GrantSource,
) -> Self {
Self {
grant_id: grant_id.into(),
principal,
resource,
effect: AccessEffect::Allow,
permissions: Vec::new(),
data_classes: Vec::new(),
tool_patterns: Vec::new(),
grant_source,
source_principal: None,
expires_at_ms: None,
delegation_id: None,
}
}
pub fn with_permissions(mut self, permissions: Vec<AccessPermission>) -> Self {
self.permissions = permissions;
self
}
pub fn with_effect(mut self, effect: AccessEffect) -> Self {
self.effect = effect;
self
}
pub fn with_data_classes(mut self, data_classes: Vec<DataClass>) -> Self {
self.data_classes = data_classes;
self
}
pub fn with_tool_patterns(mut self, tool_patterns: Vec<String>) -> Self {
self.tool_patterns = tool_patterns;
self
}
pub fn with_source_principal(mut self, source_principal: PrincipalRef) -> Self {
self.source_principal = Some(source_principal);
self
}
pub fn with_expires_at_ms(mut self, expires_at_ms: u64) -> Self {
self.expires_at_ms = Some(expires_at_ms);
self
}
pub fn with_delegation_id(mut self, delegation_id: impl Into<String>) -> Self {
self.delegation_id = Some(delegation_id.into());
self
}
pub fn has_permission(&self, permission: AccessPermission) -> bool {
self.permissions.contains(&permission)
}
pub fn allows_data_class(&self, data_class: DataClass) -> bool {
self.data_classes.contains(&data_class)
}
pub fn is_expired_at(&self, now_ms: u64) -> bool {
self.expires_at_ms
.map(|expires_at_ms| expires_at_ms <= now_ms)
.unwrap_or(false)
}
pub fn applies_to(
&self,
resource: &ResourceRef,
permission: AccessPermission,
data_class: DataClass,
now_ms: u64,
) -> bool {
!self.is_expired_at(now_ms)
&& self.has_permission(permission)
&& self.allows_data_class(data_class)
&& self.resource.applies_to(resource)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct LocalImplicitTenant;
impl LocalImplicitTenant {
pub const ORG_ID: &'static str = "local";
pub const WORKSPACE_ID: &'static str = "local";
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TenantContext {
pub org_id: String,
pub workspace_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deployment_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actor_id: Option<String>,
#[serde(default)]
pub source: TenantSource,
}
impl Default for TenantContext {
fn default() -> Self {
Self::local_implicit()
}
}
impl TenantContext {
pub fn local_implicit() -> Self {
Self {
org_id: LocalImplicitTenant::ORG_ID.to_string(),
workspace_id: LocalImplicitTenant::WORKSPACE_ID.to_string(),
deployment_id: None,
actor_id: None,
source: TenantSource::LocalImplicit,
}
}
pub fn explicit(
org_id: impl Into<String>,
workspace_id: impl Into<String>,
actor_id: Option<String>,
) -> Self {
Self {
org_id: org_id.into(),
workspace_id: workspace_id.into(),
deployment_id: None,
actor_id,
source: TenantSource::Explicit,
}
}
pub fn explicit_user_workspace(
org_id: impl Into<String>,
workspace_id: impl Into<String>,
deployment_id: Option<String>,
actor_id: impl Into<String>,
) -> Self {
Self {
org_id: org_id.into(),
workspace_id: workspace_id.into(),
deployment_id,
actor_id: Some(actor_id.into()),
source: TenantSource::Explicit,
}
}
pub fn is_local_implicit(&self) -> bool {
self.source == TenantSource::LocalImplicit
&& self.org_id == LocalImplicitTenant::ORG_ID
&& self.workspace_id == LocalImplicitTenant::WORKSPACE_ID
&& self.deployment_id.is_none()
&& self.actor_id.is_none()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VerifiedTenantContext {
pub tenant_context: TenantContext,
pub human_actor: HumanActor,
pub authority_chain: AuthorityChain,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub roles: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub org_units: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub policy_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strict_projection: Option<StrictTenantContext>,
pub issuer: String,
pub audience: String,
pub issued_at_ms: u64,
pub expires_at_ms: u64,
pub assertion_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TenantContextAssertionHeader {
pub alg: String,
pub typ: String,
pub kid: String,
}
impl TenantContextAssertionHeader {
pub fn ed25519(key_id: impl Into<String>) -> Self {
Self {
alg: "EdDSA".to_string(),
typ: "tandem-tenant-context+jws".to_string(),
kid: key_id.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TenantContextAssertionClaims {
pub version: String,
pub issuer: String,
pub audience: String,
pub issued_at_ms: u64,
pub expires_at_ms: u64,
pub assertion_id: String,
pub tenant_context: TenantContext,
pub human_actor: HumanActor,
pub authority_chain: AuthorityChain,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub roles: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub org_units: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub policy_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub principal: Option<PrincipalRef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_scope: Option<ResourceScope>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grants: Vec<ScopedGrant>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data_boundary: Option<DataBoundary>,
}
impl TenantContextAssertionClaims {
#[allow(clippy::too_many_arguments)]
pub fn new_v1(
issuer: impl Into<String>,
audience: impl Into<String>,
issued_at_ms: u64,
expires_at_ms: u64,
assertion_id: impl Into<String>,
tenant_context: TenantContext,
human_actor: HumanActor,
authority_chain: AuthorityChain,
roles: Vec<String>,
) -> Self {
Self {
version: "v1".to_string(),
issuer: issuer.into(),
audience: audience.into(),
issued_at_ms,
expires_at_ms,
assertion_id: assertion_id.into(),
tenant_context,
human_actor,
authority_chain,
roles,
org_units: Vec::new(),
capabilities: Vec::new(),
policy_version: None,
principal: None,
resource_scope: None,
grants: Vec::new(),
data_boundary: None,
}
}
pub fn is_expired_at(&self, now_ms: u64) -> bool {
self.expires_at_ms <= now_ms
}
pub fn with_strict_projection(
mut self,
principal: PrincipalRef,
resource_scope: ResourceScope,
grants: Vec<ScopedGrant>,
data_boundary: DataBoundary,
) -> Self {
self.principal = Some(principal);
self.resource_scope = Some(resource_scope);
self.grants = grants;
self.data_boundary = Some(data_boundary);
self
}
pub fn has_strict_projection(&self) -> bool {
self.principal.is_some()
|| self.resource_scope.is_some()
|| !self.grants.is_empty()
|| self.data_boundary.is_some()
}
pub fn strict_projection(&self) -> Option<StrictTenantContext> {
Some(
StrictTenantContext::new(
self.tenant_context.clone(),
self.principal.clone()?,
self.authority_chain.clone(),
self.resource_scope.clone()?,
AssertionMetadata::from(self),
)
.with_grants(self.grants.clone())
.with_data_boundary(self.data_boundary.clone().unwrap_or_default()),
)
}
}
impl From<&TenantContextAssertionClaims> for AssertionMetadata {
fn from(claims: &TenantContextAssertionClaims) -> Self {
Self {
issuer: claims.issuer.clone(),
audience: claims.audience.clone(),
issued_at_ms: claims.issued_at_ms,
expires_at_ms: claims.expires_at_ms,
assertion_id: claims.assertion_id.clone(),
key_id: None,
purpose: Some(SigningKeyPurpose::ContextAssertion),
}
}
}
impl From<TenantContextAssertionClaims> for VerifiedTenantContext {
fn from(claims: TenantContextAssertionClaims) -> Self {
let strict_projection = claims.strict_projection();
Self {
tenant_context: claims.tenant_context,
human_actor: claims.human_actor,
authority_chain: claims.authority_chain,
roles: claims.roles,
org_units: claims.org_units,
capabilities: claims.capabilities,
policy_version: claims.policy_version,
strict_projection,
issuer: claims.issuer,
audience: claims.audience,
issued_at_ms: claims.issued_at_ms,
expires_at_ms: claims.expires_at_ms,
assertion_id: claims.assertion_id,
}
}
}
impl VerifiedTenantContext {
pub fn is_expired_at(&self, now_ms: u64) -> bool {
self.expires_at_ms <= now_ms
}
pub fn tenant_matches(&self, tenant: &TenantContext) -> bool {
self.tenant_context.org_id == tenant.org_id
&& self.tenant_context.workspace_id == tenant.workspace_id
&& self.tenant_context.deployment_id == tenant.deployment_id
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DataBoundary {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_data_classes: Vec<DataClass>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub denied_data_classes: Vec<DataClass>,
}
impl DataBoundary {
pub fn unrestricted() -> Self {
Self {
allowed_data_classes: Vec::new(),
denied_data_classes: Vec::new(),
}
}
pub fn allow(data_classes: Vec<DataClass>) -> Self {
Self {
allowed_data_classes: data_classes,
denied_data_classes: Vec::new(),
}
}
pub fn allows(&self, data_class: DataClass) -> bool {
if self.denied_data_classes.contains(&data_class) {
return false;
}
self.allowed_data_classes.is_empty() || self.allowed_data_classes.contains(&data_class)
}
}
impl Default for DataBoundary {
fn default() -> Self {
Self::unrestricted()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SigningKeyPurpose {
ContextAssertion,
ApprovalReceipt,
DelegationProjection,
A2aPeerAssertion,
BreakGlassAdminAssertion,
}
impl SigningKeyPurpose {
pub fn as_str(self) -> &'static str {
match self {
Self::ContextAssertion => "context_assertion",
Self::ApprovalReceipt => "approval_receipt",
Self::DelegationProjection => "delegation_projection",
Self::A2aPeerAssertion => "a2a_peer_assertion",
Self::BreakGlassAdminAssertion => "break_glass_admin_assertion",
}
}
pub fn parse(value: &str) -> Result<Self, ParseSigningKeyPurposeError> {
value.parse()
}
}
impl core::str::FromStr for SigningKeyPurpose {
type Err = ParseSigningKeyPurposeError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.trim().to_ascii_lowercase().as_str() {
"context_assertion"
| "context-assertion"
| "tenant_context_assertion"
| "tenant-context-assertion" => Ok(Self::ContextAssertion),
"approval_receipt" | "approval-receipt" => Ok(Self::ApprovalReceipt),
"delegation_projection" | "delegation-projection" => Ok(Self::DelegationProjection),
"a2a_peer_assertion"
| "a2a-peer-assertion"
| "agent2agent_peer_assertion"
| "agent2agent-peer-assertion" => Ok(Self::A2aPeerAssertion),
"break_glass_admin_assertion"
| "break-glass-admin-assertion"
| "break_glass_admin"
| "break-glass-admin" => Ok(Self::BreakGlassAdminAssertion),
_ => Err(ParseSigningKeyPurposeError),
}
}
}
impl core::fmt::Display for SigningKeyPurpose {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParseSigningKeyPurposeError;
impl core::fmt::Display for ParseSigningKeyPurposeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("invalid signing key purpose")
}
}
impl std::error::Error for ParseSigningKeyPurposeError {}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssertionMetadata {
pub issuer: String,
pub audience: String,
pub issued_at_ms: u64,
pub expires_at_ms: u64,
pub assertion_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub purpose: Option<SigningKeyPurpose>,
}
impl AssertionMetadata {
pub fn new(
issuer: impl Into<String>,
audience: impl Into<String>,
issued_at_ms: u64,
expires_at_ms: u64,
assertion_id: impl Into<String>,
) -> Self {
Self {
issuer: issuer.into(),
audience: audience.into(),
issued_at_ms,
expires_at_ms,
assertion_id: assertion_id.into(),
key_id: None,
purpose: None,
}
}
pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
self.key_id = Some(key_id.into());
self
}
pub fn with_purpose(mut self, purpose: SigningKeyPurpose) -> Self {
self.purpose = Some(purpose);
self
}
pub fn is_expired_at(&self, now_ms: u64) -> bool {
self.expires_at_ms <= now_ms
}
}
impl From<&VerifiedTenantContext> for AssertionMetadata {
fn from(context: &VerifiedTenantContext) -> Self {
Self {
issuer: context.issuer.clone(),
audience: context.audience.clone(),
issued_at_ms: context.issued_at_ms,
expires_at_ms: context.expires_at_ms,
assertion_id: context.assertion_id.clone(),
key_id: None,
purpose: Some(SigningKeyPurpose::ContextAssertion),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StrictTenantContext {
pub tenant_context: TenantContext,
pub principal: PrincipalRef,
pub authority_chain: AuthorityChain,
pub resource_scope: ResourceScope,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grants: Vec<ScopedGrant>,
#[serde(default)]
pub data_boundary: DataBoundary,
pub assertion: AssertionMetadata,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccessDecision {
Allow,
Deny,
NotApplicable,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GrantEvaluation {
pub decision: AccessDecision,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub grant_id: Option<String>,
pub reason: String,
}
impl GrantEvaluation {
pub fn allow(grant_id: impl Into<String>) -> Self {
Self {
decision: AccessDecision::Allow,
grant_id: Some(grant_id.into()),
reason: "matching_allow_grant".to_string(),
}
}
pub fn deny(reason: impl Into<String>, grant_id: Option<String>) -> Self {
Self {
decision: AccessDecision::Deny,
grant_id,
reason: reason.into(),
}
}
pub fn not_applicable(reason: impl Into<String>) -> Self {
Self {
decision: AccessDecision::NotApplicable,
grant_id: None,
reason: reason.into(),
}
}
}
impl StrictTenantContext {
pub fn new(
tenant_context: TenantContext,
principal: PrincipalRef,
authority_chain: AuthorityChain,
resource_scope: ResourceScope,
assertion: AssertionMetadata,
) -> Self {
Self {
tenant_context,
principal,
authority_chain,
resource_scope,
grants: Vec::new(),
data_boundary: DataBoundary::default(),
assertion,
}
}
pub fn with_grants(mut self, grants: Vec<ScopedGrant>) -> Self {
self.grants = grants;
self
}
pub fn with_data_boundary(mut self, data_boundary: DataBoundary) -> Self {
self.data_boundary = data_boundary;
self
}
pub fn is_expired_at(&self, now_ms: u64) -> bool {
self.assertion.is_expired_at(now_ms)
}
pub fn allows_data_class(&self, data_class: DataClass) -> bool {
self.data_boundary.allows(data_class)
&& self
.grants
.iter()
.any(|grant| grant.allows_data_class(data_class))
}
pub fn has_permission(&self, permission: AccessPermission) -> bool {
self.grants
.iter()
.any(|grant| grant.has_permission(permission))
}
pub fn evaluate_access(
&self,
resource: &ResourceRef,
permission: AccessPermission,
data_class: DataClass,
now_ms: u64,
) -> GrantEvaluation {
if self.is_expired_at(now_ms) {
return GrantEvaluation::deny("context_expired", None);
}
if !self.data_boundary.allows(data_class) {
return GrantEvaluation::deny("data_class_denied_by_boundary", None);
}
if self.resource_scope.explicitly_denies(resource) {
return GrantEvaluation::deny("resource_explicitly_denied_by_scope", None);
}
if let Some(grant) = self.grants.iter().find(|grant| {
grant.effect == AccessEffect::Deny
&& grant.applies_to(resource, permission, data_class, now_ms)
}) {
return GrantEvaluation::deny("matching_deny_grant", Some(grant.grant_id.clone()));
}
if !self.resource_scope.contains(resource) {
return GrantEvaluation::not_applicable("resource_outside_projected_scope");
}
if let Some(grant) = self.grants.iter().find(|grant| {
grant.effect == AccessEffect::Allow
&& grant.applies_to(resource, permission, data_class, now_ms)
}) {
return GrantEvaluation::allow(grant.grant_id.clone());
}
GrantEvaluation::not_applicable("no_matching_allow_grant")
}
}
impl From<LocalImplicitTenant> for TenantContext {
fn from(_: LocalImplicitTenant) -> Self {
Self::local_implicit()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretRef {
pub org_id: String,
pub workspace_id: String,
pub provider: String,
pub secret_id: String,
pub name: String,
}
impl SecretRef {
pub fn validate_for_tenant(&self, ctx: &TenantContext) -> Result<(), SecretRefError> {
if self.org_id != ctx.org_id {
return Err(SecretRefError::OrgMismatch);
}
if self.workspace_id != ctx.workspace_id {
return Err(SecretRefError::WorkspaceMismatch);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SecretRefError {
OrgMismatch,
WorkspaceMismatch,
NotFound,
}
impl core::fmt::Display for SecretRefError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::OrgMismatch => write!(f, "secret org does not match request context"),
Self::WorkspaceMismatch => write!(f, "secret workspace does not match request context"),
Self::NotFound => write!(f, "secret not found"),
}
}
}
impl std::error::Error for SecretRefError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ConnectorLifecycleState {
#[default]
Active,
Paused,
Revoked,
Quarantined,
}
impl ConnectorLifecycleState {
pub fn allows_ingestion(self) -> bool {
matches!(self, Self::Active)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ConnectorCredentialClass {
#[default]
ReadOnly,
ReadWrite,
Admin,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConnectorCredentialRef {
pub org_id: String,
pub workspace_id: String,
pub connector_id: String,
pub credential_id: String,
#[serde(default)]
pub credential_class: ConnectorCredentialClass,
pub secret_ref: SecretRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_bound_resource: Option<ResourceRef>,
pub created_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rotated_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at_ms: Option<u64>,
}
impl ConnectorCredentialRef {
pub fn read_only(
org_id: impl Into<String>,
workspace_id: impl Into<String>,
connector_id: impl Into<String>,
credential_id: impl Into<String>,
secret_ref: SecretRef,
created_at_ms: u64,
) -> Self {
Self {
org_id: org_id.into(),
workspace_id: workspace_id.into(),
connector_id: connector_id.into(),
credential_id: credential_id.into(),
credential_class: ConnectorCredentialClass::ReadOnly,
secret_ref,
source_bound_resource: None,
created_at_ms,
rotated_at_ms: None,
expires_at_ms: None,
}
}
pub fn with_source_bound_resource(mut self, resource: ResourceRef) -> Self {
self.source_bound_resource = Some(resource);
self
}
pub fn validate_for_tenant(&self, ctx: &TenantContext) -> Result<(), SecretRefError> {
if self.org_id != ctx.org_id {
return Err(SecretRefError::OrgMismatch);
}
if self.workspace_id != ctx.workspace_id {
return Err(SecretRefError::WorkspaceMismatch);
}
self.secret_ref.validate_for_tenant(ctx)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConnectorInstance {
pub connector_id: String,
pub tenant_context: TenantContext,
pub provider: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(default)]
pub state: ConnectorLifecycleState,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub credential_refs: Vec<ConnectorCredentialRef>,
pub created_by: PrincipalRef,
pub created_at_ms: u64,
pub updated_at_ms: u64,
}
impl ConnectorInstance {
pub fn active(
connector_id: impl Into<String>,
tenant_context: TenantContext,
provider: impl Into<String>,
created_by: PrincipalRef,
now_ms: u64,
) -> Self {
Self {
connector_id: connector_id.into(),
tenant_context,
provider: provider.into(),
display_name: None,
state: ConnectorLifecycleState::Active,
credential_refs: Vec::new(),
created_by,
created_at_ms: now_ms,
updated_at_ms: now_ms,
}
}
pub fn with_state(mut self, state: ConnectorLifecycleState, updated_at_ms: u64) -> Self {
self.state = state;
self.updated_at_ms = updated_at_ms;
self
}
pub fn with_credential_refs(mut self, credential_refs: Vec<ConnectorCredentialRef>) -> Self {
self.credential_refs = credential_refs;
self
}
pub fn tenant_matches(&self, tenant: &TenantContext) -> bool {
self.tenant_context.org_id == tenant.org_id
&& self.tenant_context.workspace_id == tenant.workspace_id
&& self.tenant_context.deployment_id == tenant.deployment_id
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SourceBindingState {
#[default]
Enabled,
Disabled,
Quarantined,
}
impl SourceBindingState {
pub fn allows_ingestion(self) -> bool {
matches!(self, Self::Enabled)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IngestionPolicy {
#[serde(default = "default_true")]
pub allow_indexing: bool,
#[serde(default = "default_true")]
pub allow_prompt_context: bool,
#[serde(default)]
pub require_review: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_depth: Option<u32>,
}
impl Default for IngestionPolicy {
fn default() -> Self {
Self {
allow_indexing: true,
allow_prompt_context: true,
require_review: false,
max_depth: None,
}
}
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceBinding {
pub binding_id: String,
pub tenant_context: TenantContext,
pub connector_id: String,
pub source_type: String,
pub native_source_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_root_label: Option<String>,
pub resource_ref: ResourceRef,
pub data_class: DataClass,
#[serde(default)]
pub state: SourceBindingState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub credential_ref_id: Option<String>,
#[serde(default)]
pub ingestion_policy: IngestionPolicy,
pub created_by: PrincipalRef,
pub created_at_ms: u64,
pub updated_at_ms: u64,
}
impl SourceBinding {
#[allow(clippy::too_many_arguments)]
pub fn enabled(
binding_id: impl Into<String>,
tenant_context: TenantContext,
connector_id: impl Into<String>,
source_type: impl Into<String>,
native_source_id: impl Into<String>,
resource_ref: ResourceRef,
data_class: DataClass,
created_by: PrincipalRef,
now_ms: u64,
) -> Self {
Self {
binding_id: binding_id.into(),
tenant_context,
connector_id: connector_id.into(),
source_type: source_type.into(),
native_source_id: native_source_id.into(),
source_root_label: None,
resource_ref,
data_class,
state: SourceBindingState::Enabled,
credential_ref_id: None,
ingestion_policy: IngestionPolicy::default(),
created_by,
created_at_ms: now_ms,
updated_at_ms: now_ms,
}
}
pub fn with_state(mut self, state: SourceBindingState, updated_at_ms: u64) -> Self {
self.state = state;
self.updated_at_ms = updated_at_ms;
self
}
pub fn with_credential_ref_id(mut self, credential_ref_id: impl Into<String>) -> Self {
self.credential_ref_id = Some(credential_ref_id.into());
self
}
pub fn with_ingestion_policy(mut self, ingestion_policy: IngestionPolicy) -> Self {
self.ingestion_policy = ingestion_policy;
self
}
pub fn tenant_matches(&self, tenant: &TenantContext) -> bool {
self.tenant_context.org_id == tenant.org_id
&& self.tenant_context.workspace_id == tenant.workspace_id
&& self.tenant_context.deployment_id == tenant.deployment_id
}
pub fn can_ingest_with(&self, connector: &ConnectorInstance) -> bool {
self.connector_id == connector.connector_id
&& connector.tenant_matches(&self.tenant_context)
&& connector.state.allows_ingestion()
&& self.state.allows_ingestion()
&& self.ingestion_policy.allow_indexing
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceObject {
pub source_object_id: String,
pub tenant_context: TenantContext,
pub binding_id: String,
pub connector_id: String,
pub native_object_id: String,
pub resource_ref: ResourceRef,
pub data_class: DataClass,
#[serde(default)]
pub lifecycle_state: SourceObjectLifecycleState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub native_object_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_source_object_id: Option<String>,
#[serde(default)]
pub created_at_ms: u64,
#[serde(default)]
pub updated_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_seen_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lifecycle_changed_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub superseded_by_source_object_id: Option<String>,
}
impl SourceObject {
pub fn tenant_matches(&self, tenant: &TenantContext) -> bool {
self.tenant_context.org_id == tenant.org_id
&& self.tenant_context.workspace_id == tenant.workspace_id
&& self.tenant_context.deployment_id == tenant.deployment_id
}
pub fn is_active(&self) -> bool {
self.lifecycle_state == SourceObjectLifecycleState::Active
}
pub fn allows_prompt_context(&self) -> bool {
self.is_active()
}
pub fn with_lifecycle_state(
mut self,
lifecycle_state: SourceObjectLifecycleState,
updated_at_ms: u64,
) -> Self {
self.lifecycle_state = lifecycle_state;
self.updated_at_ms = updated_at_ms;
self.lifecycle_changed_at_ms = Some(updated_at_ms);
self
}
pub fn dedupe_scope_key(&self) -> String {
format!(
"{}:{}:{}:{}:{}:{}",
self.tenant_context.org_id,
self.tenant_context.workspace_id,
self.resource_ref.resource_kind as u8,
self.resource_ref.resource_id,
self.binding_id,
self.native_object_id
)
}
pub fn lifecycle_identity_key(&self) -> String {
format!(
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
self.tenant_context.org_id,
self.tenant_context.workspace_id,
self.tenant_context.deployment_id.as_deref().unwrap_or(""),
self.binding_id,
self.connector_id,
self.resource_ref.resource_kind as u8,
self.resource_ref.resource_id,
self.resource_ref.path_prefix.as_deref().unwrap_or(""),
self.data_class as u8,
self.native_object_id,
self.native_object_path.as_deref().unwrap_or("")
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SourceObjectLifecycleState {
#[default]
Active,
Quarantined,
Tombstoned,
Deleted,
Rescoped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum IngestionJobState {
#[default]
Queued,
Running,
Completed,
Failed,
Skipped,
Quarantined,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IngestionJob {
pub job_id: String,
pub tenant_context: TenantContext,
pub connector_id: String,
pub binding_id: String,
#[serde(default)]
pub state: IngestionJobState,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub source_object_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub started_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub finished_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub quarantine_id: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QuarantineDisposition {
Release,
Delete,
Reindex,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IngestionQuarantine {
pub quarantine_id: String,
pub tenant_context: TenantContext,
pub connector_id: String,
pub binding_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub source_object_ids: Vec<String>,
pub reason: String,
pub created_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reviewed_by: Option<PrincipalRef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reviewed_at_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disposition: Option<QuarantineDisposition>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScopedMemoryChunkRef {
pub chunk_id: String,
pub tenant_context: TenantContext,
pub source_object_id: String,
pub resource_ref: ResourceRef,
pub data_class: DataClass,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_hash: Option<String>,
}
pub trait TenantContextResolver: Send + Sync {
fn resolve_tenant_context(
&self,
org_id: Option<&str>,
workspace_id: Option<&str>,
actor_id: Option<&str>,
) -> TenantContext;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HeaderTenantContextResolver;
impl TenantContextResolver for HeaderTenantContextResolver {
fn resolve_tenant_context(
&self,
org_id: Option<&str>,
workspace_id: Option<&str>,
actor_id: Option<&str>,
) -> TenantContext {
let org_id = org_id
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(LocalImplicitTenant::ORG_ID);
let workspace_id = workspace_id
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(LocalImplicitTenant::WORKSPACE_ID);
let actor_id = actor_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string);
if org_id == LocalImplicitTenant::ORG_ID
&& workspace_id == LocalImplicitTenant::WORKSPACE_ID
&& actor_id.is_none()
{
TenantContext::local_implicit()
} else {
TenantContext::explicit(org_id.to_string(), workspace_id.to_string(), actor_id)
}
}
}
pub trait RequestAuthorizationHook: Send + Sync {
fn authorize(&self, principal: &RequestPrincipal, tenant: &TenantContext) -> bool;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopRequestAuthorizationHook;
impl RequestAuthorizationHook for NoopRequestAuthorizationHook {
fn authorize(&self, _principal: &RequestPrincipal, _tenant: &TenantContext) -> bool {
true
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EnterpriseStatus {
pub mode: EnterpriseMode,
pub bridge_state: EnterpriseBridgeState,
#[serde(default)]
pub capabilities: Vec<EnterpriseCapability>,
pub tenant_context: TenantContext,
pub public_build: bool,
pub contract_version: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub notes: Vec<String>,
}
impl EnterpriseStatus {
pub fn public_oss() -> Self {
Self {
mode: EnterpriseMode::Disabled,
bridge_state: EnterpriseBridgeState::Absent,
capabilities: vec![
EnterpriseCapability::Status,
EnterpriseCapability::TenantContext,
],
tenant_context: TenantContext::local_implicit(),
public_build: true,
contract_version: "v1".to_string(),
notes: vec![
"enterprise bridge is not configured".to_string(),
"OSS mode uses a local implicit tenant until enterprise mode is enabled"
.to_string(),
],
}
}
}
pub trait EnterpriseBridge: Send + Sync {
fn status(&self) -> EnterpriseStatus;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopEnterpriseBridge;
impl EnterpriseBridge for NoopEnterpriseBridge {
fn status(&self) -> EnterpriseStatus {
EnterpriseStatus::public_oss()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secret_ref_validation_rejects_cross_tenant_access() {
let secret_ref = SecretRef {
org_id: "org-a".to_string(),
workspace_id: "workspace-a".to_string(),
provider: "mcp_header".to_string(),
secret_id: "secret-a".to_string(),
name: "authorization".to_string(),
};
let tenant = TenantContext::explicit("org-a", "workspace-a", None);
assert!(secret_ref.validate_for_tenant(&tenant).is_ok());
let wrong_workspace = TenantContext::explicit("org-a", "workspace-b", None);
assert!(matches!(
secret_ref.validate_for_tenant(&wrong_workspace),
Err(SecretRefError::WorkspaceMismatch)
));
}
#[test]
fn explicit_user_workspace_preserves_actor_and_deployment() {
let tenant = TenantContext::explicit_user_workspace(
"org-a",
"workspace-a",
Some("deployment-a".to_string()),
"user-a",
);
assert_eq!(tenant.org_id, "org-a");
assert_eq!(tenant.workspace_id, "workspace-a");
assert_eq!(tenant.deployment_id.as_deref(), Some("deployment-a"));
assert_eq!(tenant.actor_id.as_deref(), Some("user-a"));
assert_eq!(tenant.source, TenantSource::Explicit);
assert!(!tenant.is_local_implicit());
}
#[test]
fn authority_chain_from_request_executes_as_same_actor() {
let principal = RequestPrincipal::authenticated_user("user-a", "tandem_web");
let chain = AuthorityChain::from_request(principal.clone());
assert_eq!(chain.initiated_by, principal);
assert!(chain.owned_by.is_none());
assert!(chain.approved_by.is_none());
assert_eq!(chain.executed_as, ExecutionPrincipal::Request(principal));
}
#[test]
fn verified_tenant_context_checks_expiry_and_tenant_match() {
let tenant = TenantContext::explicit_user_workspace(
"org-a",
"workspace-a",
Some("deployment-a".to_string()),
"user-a",
);
let actor = HumanActor::tandem_user("user-a");
let principal = RequestPrincipal::authenticated_user("user-a", "tandem_web");
let verified = VerifiedTenantContext {
tenant_context: tenant.clone(),
human_actor: actor,
authority_chain: AuthorityChain::from_request(principal),
roles: vec!["owner".to_string()],
org_units: Vec::new(),
capabilities: Vec::new(),
policy_version: None,
strict_projection: None,
issuer: "tandem-web".to_string(),
audience: "tandem-runtime".to_string(),
issued_at_ms: 100,
expires_at_ms: 200,
assertion_id: "assertion-1".to_string(),
};
assert!(!verified.is_expired_at(199));
assert!(verified.is_expired_at(200));
assert!(verified.tenant_matches(&tenant));
assert!(!verified.tenant_matches(&TenantContext::explicit(
"org-b",
"workspace-a",
Some("user-a".to_string()),
)));
}
#[test]
fn runtime_auth_mode_parses_operator_aliases() {
assert_eq!(
RuntimeAuthMode::parse("local"),
Ok(RuntimeAuthMode::LocalSingleTenant)
);
assert_eq!(
RuntimeAuthMode::parse("hosted-single-tenant"),
Ok(RuntimeAuthMode::HostedSingleTenant)
);
assert_eq!(
RuntimeAuthMode::parse("enterprise_required"),
Ok(RuntimeAuthMode::EnterpriseRequired)
);
assert!(RuntimeAuthMode::parse("definitely-not-a-mode").is_err());
assert_eq!(
RuntimeAuthMode::EnterpriseRequired.to_string(),
"enterprise_required"
);
}
#[test]
fn tenant_context_assertion_claims_convert_to_verified_context() {
let tenant = TenantContext::explicit_user_workspace(
"org-a",
"workspace-a",
Some("deployment-a".to_string()),
"user-a",
);
let actor = HumanActor::tandem_user("user-a");
let principal = RequestPrincipal::authenticated_user("user-a", "tandem_web");
let chain = AuthorityChain::from_request(principal);
let claims = TenantContextAssertionClaims::new_v1(
"tandem-web",
"tandem-runtime",
100,
200,
"assertion-1",
tenant.clone(),
actor.clone(),
chain.clone(),
vec!["operator".to_string(), "approver".to_string()],
);
assert_eq!(claims.version, "v1");
assert!(!claims.is_expired_at(199));
assert!(claims.is_expired_at(200));
let verified = VerifiedTenantContext::from(claims);
assert_eq!(verified.tenant_context, tenant);
assert_eq!(verified.human_actor, actor);
assert_eq!(verified.authority_chain, chain);
assert_eq!(verified.roles, vec!["operator", "approver"]);
assert_eq!(verified.issuer, "tandem-web");
assert_eq!(verified.audience, "tandem-runtime");
assert_eq!(verified.assertion_id, "assertion-1");
}
#[test]
fn tenant_context_assertion_claims_can_carry_strict_projection() {
let tenant = TenantContext::explicit_user_workspace(
"acme",
"engineering",
Some("deployment-prod".to_string()),
"user-eng",
);
let actor = HumanActor::tandem_user("user-eng");
let request_principal = RequestPrincipal::authenticated_user("user-eng", "tandem-web");
let authority_chain = AuthorityChain::from_request(request_principal);
let principal =
PrincipalRef::agent_worker("agent-platform").with_tenant_actor_id("user-eng");
let project = ResourceRef::new("acme", "engineering", ResourceKind::Project, "platform");
let repo = ResourceRef::new("acme", "engineering", ResourceKind::Repository, "tandem")
.with_project_id("platform")
.with_path_prefix("crates/tandem-enterprise-contract/");
let grant = ScopedGrant::new(
"grant-platform-read",
principal.clone(),
repo.clone(),
GrantSource::Delegation,
)
.with_permissions(vec![AccessPermission::View, AccessPermission::Read])
.with_data_classes(vec![DataClass::SourceCode])
.with_delegation_id("delegation-platform");
let claims = TenantContextAssertionClaims::new_v1(
"tandem-web",
"tandem-runtime",
1_000,
2_000,
"assertion-platform",
tenant.clone(),
actor,
authority_chain,
vec![],
)
.with_strict_projection(
principal.clone(),
ResourceScope {
root: project,
allowed_resources: vec![repo.clone()],
denied_resources: Vec::new(),
max_depth: Some(4),
},
vec![grant],
DataBoundary::allow(vec![DataClass::SourceCode]),
);
assert!(claims.has_strict_projection());
let encoded = serde_json::to_value(&claims).expect("serialize projected claims");
assert_eq!(encoded["principal"]["kind"], "agent_worker");
assert_eq!(
encoded["resource_scope"]["allowed_resources"][0]["resource_kind"],
"repository"
);
assert_eq!(encoded["grants"][0]["delegation_id"], "delegation-platform");
let decoded: TenantContextAssertionClaims =
serde_json::from_value(encoded).expect("deserialize projected claims");
let strict = decoded
.strict_projection()
.expect("strict projection should be present");
assert_eq!(strict.tenant_context, tenant);
assert_eq!(strict.principal, principal);
assert_eq!(strict.grants[0].grant_id, "grant-platform-read");
assert_eq!(strict.assertion.assertion_id, "assertion-platform");
assert!(strict.allows_data_class(DataClass::SourceCode));
assert!(!strict.allows_data_class(DataClass::Executive));
}
#[test]
fn tenant_context_assertion_claims_remain_backward_compatible_without_projection() {
let legacy = serde_json::json!({
"version": "v1",
"issuer": "tandem-web",
"audience": "tandem-runtime",
"issued_at_ms": 1000,
"expires_at_ms": 2000,
"assertion_id": "assertion-legacy",
"tenant_context": {
"org_id": "acme",
"workspace_id": "engineering",
"deployment_id": "deployment-prod",
"actor_id": "user-eng",
"source": "explicit"
},
"human_actor": {
"actor_id": "user-eng",
"provider": "tandem"
},
"authority_chain": {
"initiated_by": {
"actor_id": "user-eng",
"source": "tandem-web"
},
"executed_as": {
"kind": "request",
"actor_id": "user-eng",
"source": "tandem-web"
}
}
});
let claims: TenantContextAssertionClaims =
serde_json::from_value(legacy).expect("legacy claims should deserialize");
assert!(!claims.has_strict_projection());
assert!(claims.strict_projection().is_none());
assert!(claims.grants.is_empty());
assert!(claims.data_boundary.is_none());
}
#[test]
fn tenant_context_assertion_header_uses_eddsa_jws_typ() {
let header = TenantContextAssertionHeader::ed25519("key-1");
assert_eq!(header.alg, "EdDSA");
assert_eq!(header.typ, "tandem-tenant-context+jws");
assert_eq!(header.kid, "key-1");
}
#[test]
fn header_resolver_defaults_to_local_tenant() {
let resolver = HeaderTenantContextResolver;
let tenant = resolver.resolve_tenant_context(None, None, None);
assert!(tenant.is_local_implicit());
}
#[test]
fn request_authorization_hook_is_noop_by_default() {
let hook = NoopRequestAuthorizationHook;
let principal = RequestPrincipal::anonymous();
let tenant = TenantContext::local_implicit();
assert!(hook.authorize(&principal, &tenant));
}
#[test]
fn resource_ref_round_trips_finance_workspace_data_store() {
let resource =
ResourceRef::new("acme", "finance", ResourceKind::DataStore, "finance-ledger")
.with_parent_path(vec![
ResourcePathSegment::named(ResourceKind::Department, "finance", "Finance"),
ResourcePathSegment::named(
ResourceKind::SharedDrive,
"finance-drive",
"Finance",
),
]);
let encoded = serde_json::to_string(&resource).expect("serialize resource ref");
assert!(encoded.contains("\"resource_kind\":\"data_store\""));
let decoded: ResourceRef =
serde_json::from_str(&encoded).expect("deserialize resource ref");
assert_eq!(decoded, resource);
assert_eq!(decoded.organization_id, "acme");
assert_eq!(decoded.workspace_id, "finance");
assert_eq!(decoded.resource_kind, ResourceKind::DataStore);
}
#[test]
fn resource_scope_models_engineering_repo_path_scope() {
let repository =
ResourceRef::new("acme", "engineering", ResourceKind::Repository, "tandem")
.with_project_id("platform")
.with_branch_id("main")
.with_path_prefix("crates/tandem-enterprise-contract/");
let scope = ResourceScope {
root: ResourceRef::new("acme", "engineering", ResourceKind::Project, "platform"),
allowed_resources: vec![repository.clone()],
denied_resources: vec![ResourceRef::new(
"acme",
"engineering",
ResourceKind::Directory,
"secrets",
)
.with_project_id("platform")
.with_path_prefix("crates/tandem-enterprise-contract/secrets/")],
max_depth: Some(4),
};
let encoded = serde_json::to_value(&scope).expect("serialize resource scope");
assert_eq!(
encoded["allowed_resources"][0]["resource_kind"],
"repository"
);
assert_eq!(
encoded["allowed_resources"][0]["path_prefix"],
"crates/tandem-enterprise-contract/"
);
let decoded: ResourceScope =
serde_json::from_value(encoded).expect("deserialize resource scope");
assert_eq!(decoded, scope);
assert_eq!(decoded.allowed_resources, vec![repository]);
}
#[test]
fn resource_scope_models_ceo_org_wide_executive_access() {
let principal = PrincipalRef::human_user("ceo-user")
.with_tenant_actor_id("user-ceo")
.with_issuer_subject("https://idp.acme.example", "00uceo");
let scope = ResourceScope::root(ResourceRef::new(
"acme",
"*",
ResourceKind::Organization,
"acme",
));
assert_eq!(principal.kind, PrincipalKind::HumanUser);
assert_eq!(principal.tenant_actor_id.as_deref(), Some("user-ceo"));
assert_eq!(scope.root.resource_kind, ResourceKind::Organization);
assert_eq!(scope.root.workspace_id, "*");
assert!(scope.allowed_resources.is_empty());
let encoded = serde_json::to_string(&DataClass::Executive).expect("serialize data class");
assert_eq!(encoded, "\"executive\"");
}
#[test]
fn mcp_tool_resource_target_and_permissions_are_transport_safe() {
let tool = ResourceRef::new(
"acme",
"security",
ResourceKind::McpTool,
"mcp:google-drive:files.export",
)
.with_parent_path(vec![
ResourcePathSegment::new(ResourceKind::McpServer, "google-drive"),
ResourcePathSegment::new(ResourceKind::DataStore, "security-drive"),
]);
let permissions = vec![AccessPermission::View, AccessPermission::Execute];
let data_classes = vec![DataClass::Confidential, DataClass::Credential];
let worker = PrincipalRef::agent_worker("agent-security-export");
let payload = serde_json::json!({
"principal": worker,
"resource": tool,
"permissions": permissions,
"data_classes": data_classes,
});
assert_eq!(payload["principal"]["kind"], "agent_worker");
assert_eq!(payload["resource"]["resource_kind"], "mcp_tool");
assert_eq!(payload["permissions"][1], "execute");
assert_eq!(payload["data_classes"][1], "credential");
}
#[test]
fn scoped_grant_models_department_membership_data_access() {
let finance_department = PrincipalRef::new(PrincipalKind::Department, "finance");
let finance_user =
PrincipalRef::human_user("user-finance").with_tenant_actor_id("actor-finance");
let finance_store =
ResourceRef::new("acme", "finance", ResourceKind::DataStore, "finance-ledger");
let grant = ScopedGrant::new(
"grant-finance-ledger-read",
finance_user,
finance_store,
GrantSource::DepartmentMembership,
)
.with_source_principal(finance_department)
.with_permissions(vec![AccessPermission::View, AccessPermission::Read])
.with_data_classes(vec![DataClass::FinancialRecord, DataClass::Confidential]);
assert_eq!(grant.grant_source, GrantSource::DepartmentMembership);
assert!(grant.has_permission(AccessPermission::Read));
assert!(!grant.has_permission(AccessPermission::Edit));
assert!(grant.allows_data_class(DataClass::FinancialRecord));
assert!(!grant.allows_data_class(DataClass::Executive));
let encoded = serde_json::to_value(&grant).expect("serialize department grant");
assert_eq!(encoded["grant_source"], "department_membership");
assert_eq!(encoded["source_principal"]["kind"], "department");
}
#[test]
fn scoped_grant_models_cross_functional_group_access() {
let launch_group = PrincipalRef::new(PrincipalKind::Group, "launch-team");
let marketer = PrincipalRef::human_user("user-marketing");
let launch_room = ResourceRef::new("acme", "gtm", ResourceKind::DataRoom, "q4-launch-room");
let grant = ScopedGrant::new(
"grant-launch-room-edit",
marketer,
launch_room,
GrantSource::GroupMembership,
)
.with_source_principal(launch_group)
.with_permissions(vec![
AccessPermission::View,
AccessPermission::Read,
AccessPermission::Edit,
])
.with_data_classes(vec![DataClass::Internal, DataClass::CustomerData]);
let decoded: ScopedGrant =
serde_json::from_value(serde_json::to_value(&grant).expect("serialize group grant"))
.expect("deserialize group grant");
assert_eq!(decoded, grant);
assert_eq!(decoded.grant_source, GrantSource::GroupMembership);
assert!(decoded.has_permission(AccessPermission::Edit));
assert!(decoded.allows_data_class(DataClass::CustomerData));
}
#[test]
fn organization_unit_taxonomy_models_company_specific_domains() {
let tenant = TenantContext::explicit_user_workspace(
"clinic-co",
"care-delivery",
Some("deployment-prod".to_string()),
"admin-user",
);
let admin = PrincipalRef::human_user("admin-user");
let doctors = OrganizationUnit::active(
"doctors",
tenant.clone(),
"Doctors",
OrganizationUnitKind::ClinicalGroup,
admin.clone(),
1_000,
)
.with_taxonomy_id("clinical_role")
.with_parent_unit_id("clinical");
let consultants = OrganizationUnit::active(
"consultants",
tenant.clone(),
"Consultants",
OrganizationUnitKind::ContractorGroup,
admin,
1_000,
);
assert_eq!(
doctors.principal_ref().kind,
PrincipalKind::OrganizationUnit
);
assert_eq!(doctors.principal_ref().id, "clinical_role/doctors");
assert_eq!(
doctors.resource_ref().resource_kind,
ResourceKind::OrganizationUnit
);
assert_eq!(doctors.resource_ref().resource_id, "clinical_role/doctors");
assert_eq!(doctors.parent_unit_id.as_deref(), Some("clinical"));
assert_eq!(consultants.kind, OrganizationUnitKind::ContractorGroup);
let encoded = serde_json::to_value(&doctors).expect("serialize organization unit");
assert_eq!(encoded["taxonomy_id"], "clinical_role");
assert_eq!(encoded["kind"], "clinical_group");
assert_eq!(encoded["state"], "active");
assert_eq!(encoded["unit_id"], "doctors");
let decoded: OrganizationUnit =
serde_json::from_value(encoded).expect("deserialize organization unit");
assert_eq!(decoded, doctors);
}
#[test]
fn organization_unit_membership_feeds_scoped_grants_without_hardcoded_roles() {
let tenant = TenantContext::explicit_user_workspace(
"clinic-co",
"care-delivery",
Some("deployment-prod".to_string()),
"doctor-user",
);
let doctors = PrincipalRef::organization_unit("clinical_role/doctors");
let doctor = PrincipalRef::human_user("doctor-user");
let membership = OrganizationUnitMembership::active(
"membership-doctor-user",
tenant,
doctors.clone(),
doctor.clone(),
OrganizationUnitMembershipSource::HostedControlPlane,
1_000,
)
.with_expires_at_ms(2_000);
let patient_cases = ResourceRef::new(
"clinic-co",
"care-delivery",
ResourceKind::DataStore,
"patient-cases",
);
let grant = ScopedGrant::new(
"grant-doctors-patient-cases",
doctor,
patient_cases.clone(),
GrantSource::OrganizationUnitMembership,
)
.with_source_principal(doctors)
.with_permissions(vec![AccessPermission::View, AccessPermission::Read])
.with_data_classes(vec![DataClass::Regulated, DataClass::CustomerData]);
assert!(membership.is_active_at(1_999));
assert!(!membership.is_active_at(2_000));
assert_eq!(grant.grant_source, GrantSource::OrganizationUnitMembership);
assert_eq!(
grant.source_principal.as_ref().map(|source| source.kind),
Some(PrincipalKind::OrganizationUnit)
);
assert!(grant.applies_to(
&patient_cases,
AccessPermission::Read,
DataClass::Regulated,
1_500
));
let encoded = serde_json::to_value(&grant).expect("serialize org unit grant");
assert_eq!(encoded["grant_source"], "organization_unit_membership");
assert_eq!(encoded["source_principal"]["kind"], "organization_unit");
}
#[test]
fn scoped_grant_models_explicit_executive_global_access() {
let ceo = PrincipalRef::human_user("ceo-user").with_tenant_actor_id("actor-ceo");
let org = ResourceRef::new("acme", "*", ResourceKind::Organization, "acme");
let grant = ScopedGrant::new("grant-ceo-global", ceo, org, GrantSource::ExecutiveGlobal)
.with_permissions(vec![
AccessPermission::View,
AccessPermission::Read,
AccessPermission::Admin,
])
.with_data_classes(vec![
DataClass::Internal,
DataClass::Confidential,
DataClass::Restricted,
DataClass::Executive,
DataClass::FinancialRecord,
]);
assert_eq!(grant.grant_source, GrantSource::ExecutiveGlobal);
assert_eq!(grant.resource.resource_kind, ResourceKind::Organization);
assert_eq!(grant.resource.workspace_id, "*");
assert!(grant.has_permission(AccessPermission::Admin));
assert!(grant.allows_data_class(DataClass::Executive));
}
#[test]
fn scoped_grant_models_down_scoped_delegation_with_expiry() {
let delegate = PrincipalRef::new(PrincipalKind::ExternalDelegate, "vendor-agent")
.with_issuer_subject("a2a://vendor.example", "vendor-agent-7");
let delegator = PrincipalRef::human_user("user-legal");
let contract_branch =
ResourceRef::new("acme", "legal", ResourceKind::Document, "vendor-contract")
.with_project_id("vendor-review")
.with_path_prefix("/contracts/vendor-a/");
let grant = ScopedGrant::new(
"grant-vendor-contract-read",
delegate,
contract_branch,
GrantSource::Delegation,
)
.with_source_principal(delegator)
.with_permissions(vec![AccessPermission::View, AccessPermission::Read])
.with_data_classes(vec![DataClass::Confidential])
.with_tool_patterns(vec!["mcp:google-drive:files.get".to_string()])
.with_delegation_id("delegation-123")
.with_expires_at_ms(2_000);
assert_eq!(grant.grant_source, GrantSource::Delegation);
assert_eq!(grant.delegation_id.as_deref(), Some("delegation-123"));
assert!(!grant.is_expired_at(1_999));
assert!(grant.is_expired_at(2_000));
assert_eq!(grant.tool_patterns, vec!["mcp:google-drive:files.get"]);
let encoded = serde_json::to_value(&grant).expect("serialize delegation grant");
assert_eq!(encoded["principal"]["kind"], "external_delegate");
assert_eq!(encoded["grant_source"], "delegation");
assert_eq!(encoded["delegation_id"], "delegation-123");
}
#[test]
fn assertion_metadata_derives_from_verified_tenant_context() {
let tenant = TenantContext::explicit_user_workspace(
"org-a",
"workspace-a",
Some("deployment-a".to_string()),
"user-a",
);
let principal = RequestPrincipal::authenticated_user("user-a", "tandem-web");
let verified = VerifiedTenantContext {
tenant_context: tenant,
human_actor: HumanActor::tandem_user("user-a"),
authority_chain: AuthorityChain::from_request(principal),
roles: vec!["enterprise:admin".to_string()],
org_units: Vec::new(),
capabilities: Vec::new(),
policy_version: None,
strict_projection: None,
issuer: "tandem-web".to_string(),
audience: "tandem-runtime".to_string(),
issued_at_ms: 1_000,
expires_at_ms: 2_000,
assertion_id: "assertion-123".to_string(),
};
let metadata = AssertionMetadata::from(&verified);
assert_eq!(metadata.issuer, "tandem-web");
assert_eq!(metadata.audience, "tandem-runtime");
assert_eq!(metadata.assertion_id, "assertion-123");
assert_eq!(metadata.purpose, Some(SigningKeyPurpose::ContextAssertion));
assert!(!metadata.is_expired_at(1_999));
assert!(metadata.is_expired_at(2_000));
}
#[test]
fn signing_key_purpose_defines_enterprise_signing_lanes() {
let purposes = vec![
SigningKeyPurpose::ContextAssertion,
SigningKeyPurpose::ApprovalReceipt,
SigningKeyPurpose::DelegationProjection,
SigningKeyPurpose::A2aPeerAssertion,
SigningKeyPurpose::BreakGlassAdminAssertion,
];
let encoded = serde_json::to_value(&purposes).expect("serialize signing key purposes");
assert_eq!(
encoded,
serde_json::json!([
"context_assertion",
"approval_receipt",
"delegation_projection",
"a2a_peer_assertion",
"break_glass_admin_assertion"
])
);
assert_eq!(
SigningKeyPurpose::parse("break-glass-admin"),
Ok(SigningKeyPurpose::BreakGlassAdminAssertion)
);
assert!(SigningKeyPurpose::parse("arbitrary_header_key").is_err());
}
#[test]
fn data_boundary_denies_explicitly_blocked_classes() {
let boundary = DataBoundary {
allowed_data_classes: vec![DataClass::Internal, DataClass::Executive],
denied_data_classes: vec![DataClass::Executive],
};
assert!(boundary.allows(DataClass::Internal));
assert!(!boundary.allows(DataClass::Executive));
assert!(!boundary.allows(DataClass::FinancialRecord));
}
#[test]
fn strict_tenant_context_round_trips_project_scoped_agent_projection() {
let tenant_context = TenantContext::explicit_user_workspace(
"acme",
"engineering",
Some("deployment-prod".to_string()),
"user-eng",
);
let request_principal = RequestPrincipal::authenticated_user("user-eng", "tandem-web");
let authority_chain = AuthorityChain::from_request(request_principal);
let agent =
PrincipalRef::agent_worker("agent-platform-fix").with_tenant_actor_id("user-eng");
let project = ResourceRef::new("acme", "engineering", ResourceKind::Project, "platform");
let repository =
ResourceRef::new("acme", "engineering", ResourceKind::Repository, "tandem")
.with_project_id("platform")
.with_path_prefix("crates/tandem-enterprise-contract/");
let resource_scope = ResourceScope {
root: project,
allowed_resources: vec![repository.clone()],
denied_resources: vec![ResourceRef::new(
"acme",
"engineering",
ResourceKind::Directory,
"restricted",
)
.with_project_id("platform")
.with_path_prefix("crates/tandem-enterprise-contract/restricted/")],
max_depth: Some(5),
};
let grant = ScopedGrant::new(
"grant-agent-platform-edit",
agent.clone(),
repository,
GrantSource::Delegation,
)
.with_permissions(vec![
AccessPermission::View,
AccessPermission::Read,
AccessPermission::Edit,
])
.with_data_classes(vec![DataClass::SourceCode, DataClass::Internal])
.with_delegation_id("delegation-platform-fix")
.with_expires_at_ms(2_000);
let context = StrictTenantContext::new(
tenant_context,
agent,
authority_chain,
resource_scope,
AssertionMetadata::new(
"tandem-web",
"tandem-runtime",
1_000,
2_000,
"assertion-platform-fix",
)
.with_key_id("deployment-prod-ctx-2026-05-01")
.with_purpose(SigningKeyPurpose::ContextAssertion),
)
.with_grants(vec![grant])
.with_data_boundary(DataBoundary::allow(vec![
DataClass::SourceCode,
DataClass::Internal,
]));
assert!(context.has_permission(AccessPermission::Edit));
assert!(!context.has_permission(AccessPermission::Execute));
assert!(context.allows_data_class(DataClass::SourceCode));
assert!(!context.allows_data_class(DataClass::Executive));
assert!(!context.is_expired_at(1_999));
assert!(context.is_expired_at(2_000));
let decoded: StrictTenantContext = serde_json::from_value(
serde_json::to_value(&context).expect("serialize strict context"),
)
.expect("deserialize strict context");
assert_eq!(decoded, context);
assert_eq!(
decoded.grants[0].delegation_id.as_deref(),
Some("delegation-platform-fix")
);
assert_eq!(
decoded.assertion.key_id.as_deref(),
Some("deployment-prod-ctx-2026-05-01")
);
}
#[test]
fn grant_evaluation_allows_department_membership_data_access() {
let finance_store =
ResourceRef::new("acme", "finance", ResourceKind::DataStore, "finance-ledger");
let principal = PrincipalRef::human_user("user-finance");
let grant = ScopedGrant::new(
"grant-finance-read",
principal.clone(),
ResourceRef::new("acme", "finance", ResourceKind::Department, "finance"),
GrantSource::DepartmentMembership,
)
.with_permissions(vec![AccessPermission::View, AccessPermission::Read])
.with_data_classes(vec![DataClass::FinancialRecord]);
let context = test_strict_context(
"finance",
principal,
ResourceScope::root(ResourceRef::new(
"acme",
"finance",
ResourceKind::Department,
"finance",
)),
vec![grant],
);
let evaluation = context.evaluate_access(
&finance_store,
AccessPermission::Read,
DataClass::FinancialRecord,
1_500,
);
assert_eq!(evaluation.decision, AccessDecision::Allow);
assert_eq!(evaluation.grant_id.as_deref(), Some("grant-finance-read"));
}
#[test]
fn grant_evaluation_deny_wins_over_org_wide_allow() {
let hr_document =
ResourceRef::new("acme", "hr", ResourceKind::Document, "compensation-plan");
let principal = PrincipalRef::human_user("user-exec");
let org_allow = ScopedGrant::new(
"grant-org-read",
principal.clone(),
ResourceRef::new("acme", "*", ResourceKind::Organization, "acme"),
GrantSource::ExecutiveGlobal,
)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::Executive]);
let hr_deny = ScopedGrant::new(
"deny-hr-comp",
principal.clone(),
ResourceRef::new("acme", "hr", ResourceKind::Document, "compensation-plan"),
GrantSource::Direct,
)
.with_effect(AccessEffect::Deny)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::Executive]);
let context = test_strict_context(
"*",
principal,
ResourceScope::root(ResourceRef::new(
"acme",
"*",
ResourceKind::Organization,
"acme",
)),
vec![org_allow, hr_deny],
)
.with_data_boundary(DataBoundary::allow(vec![DataClass::Executive]));
let evaluation = context.evaluate_access(
&hr_document,
AccessPermission::Read,
DataClass::Executive,
1_500,
);
assert_eq!(evaluation.decision, AccessDecision::Deny);
assert_eq!(evaluation.grant_id.as_deref(), Some("deny-hr-comp"));
assert_eq!(evaluation.reason, "matching_deny_grant");
}
#[test]
fn grant_evaluation_project_grant_applies_to_file_path() {
let principal = PrincipalRef::agent_worker("agent-platform");
let file = ResourceRef::new(
"acme",
"engineering",
ResourceKind::File,
"crates/tandem-enterprise-contract/src/lib.rs",
)
.with_project_id("platform")
.with_path_prefix("crates/tandem-enterprise-contract/src/lib.rs");
let grant = ScopedGrant::new(
"grant-platform-source-edit",
principal.clone(),
ResourceRef::new("acme", "engineering", ResourceKind::Project, "platform"),
GrantSource::Delegation,
)
.with_permissions(vec![AccessPermission::Read, AccessPermission::Edit])
.with_data_classes(vec![DataClass::SourceCode]);
let context = test_strict_context(
"engineering",
principal,
ResourceScope::root(ResourceRef::new(
"acme",
"engineering",
ResourceKind::Project,
"platform",
)),
vec![grant],
);
let evaluation =
context.evaluate_access(&file, AccessPermission::Edit, DataClass::SourceCode, 1_500);
assert_eq!(evaluation.decision, AccessDecision::Allow);
assert_eq!(
evaluation.grant_id.as_deref(),
Some("grant-platform-source-edit")
);
}
#[test]
fn grant_evaluation_expired_grant_does_not_apply() {
let principal = PrincipalRef::human_user("user-finance");
let finance_store =
ResourceRef::new("acme", "finance", ResourceKind::DataStore, "finance-ledger");
let grant = ScopedGrant::new(
"grant-expired-finance",
principal.clone(),
finance_store.clone(),
GrantSource::Direct,
)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::FinancialRecord])
.with_expires_at_ms(1_400);
let context = test_strict_context(
"finance",
principal,
ResourceScope::root(ResourceRef::new(
"acme",
"finance",
ResourceKind::Workspace,
"finance",
)),
vec![grant],
);
let evaluation = context.evaluate_access(
&finance_store,
AccessPermission::Read,
DataClass::FinancialRecord,
1_500,
);
assert_eq!(evaluation.decision, AccessDecision::NotApplicable);
assert_eq!(evaluation.reason, "no_matching_allow_grant");
}
#[test]
fn grant_evaluation_delegated_grant_stays_narrower_than_parent_scope() {
let principal = PrincipalRef::new(PrincipalKind::ExternalDelegate, "vendor-agent");
let allowed_doc =
ResourceRef::new("acme", "legal", ResourceKind::Document, "vendor-contract")
.with_project_id("vendor-review")
.with_path_prefix("/contracts/vendor-a/");
let other_doc = ResourceRef::new("acme", "legal", ResourceKind::Document, "board-minutes")
.with_project_id("vendor-review")
.with_path_prefix("/executive/board-minutes/");
let grant = ScopedGrant::new(
"grant-vendor-contract",
principal.clone(),
allowed_doc.clone(),
GrantSource::Delegation,
)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::Confidential])
.with_delegation_id("delegation-123");
let context = test_strict_context(
"legal",
principal,
ResourceScope {
root: ResourceRef::new("acme", "legal", ResourceKind::Project, "vendor-review"),
allowed_resources: vec![allowed_doc.clone()],
denied_resources: vec![ResourceRef::new(
"acme",
"legal",
ResourceKind::Document,
"board-minutes",
)],
max_depth: Some(2),
},
vec![grant],
);
let allowed = context.evaluate_access(
&allowed_doc,
AccessPermission::Read,
DataClass::Confidential,
1_500,
);
let denied = context.evaluate_access(
&other_doc,
AccessPermission::Read,
DataClass::Confidential,
1_500,
);
assert_eq!(allowed.decision, AccessDecision::Allow);
assert_eq!(denied.decision, AccessDecision::Deny);
assert_eq!(denied.reason, "resource_explicitly_denied_by_scope");
}
#[test]
fn department_grants_do_not_cross_resource_or_data_class_boundaries() {
let finance_user = PrincipalRef::human_user("user-finance");
let finance_grant = ScopedGrant::new(
"grant-finance-ledger-read",
finance_user.clone(),
ResourceRef::new("acme", "finance", ResourceKind::Department, "finance"),
GrantSource::DepartmentMembership,
)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::FinancialRecord]);
let finance_context = test_strict_context(
"finance",
finance_user,
ResourceScope::root(ResourceRef::new(
"acme",
"finance",
ResourceKind::Department,
"finance",
)),
vec![finance_grant],
);
let engineering_repo = ResourceRef::new(
"acme",
"engineering",
ResourceKind::Repository,
"product-api",
);
let finance_denied_engineering = finance_context.evaluate_access(
&engineering_repo,
AccessPermission::Read,
DataClass::SourceCode,
1_500,
);
assert_eq!(
finance_denied_engineering.decision,
AccessDecision::NotApplicable
);
assert_eq!(
finance_denied_engineering.reason,
"resource_outside_projected_scope"
);
let engineering_user = PrincipalRef::human_user("user-engineering");
let engineering_grant = ScopedGrant::new(
"grant-engineering-source-read",
engineering_user.clone(),
ResourceRef::new("acme", "engineering", ResourceKind::Project, "product-api"),
GrantSource::DepartmentMembership,
)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::SourceCode]);
let engineering_context = test_strict_context(
"engineering",
engineering_user,
ResourceScope::root(ResourceRef::new(
"acme",
"engineering",
ResourceKind::Project,
"product-api",
)),
vec![engineering_grant],
);
let hr_compensation =
ResourceRef::new("acme", "hr", ResourceKind::Document, "compensation-bands");
let engineering_denied_hr = engineering_context.evaluate_access(
&hr_compensation,
AccessPermission::Read,
DataClass::FinancialRecord,
1_500,
);
assert_eq!(
engineering_denied_hr.decision,
AccessDecision::NotApplicable
);
assert_eq!(
engineering_denied_hr.reason,
"resource_outside_projected_scope"
);
}
#[test]
fn executive_global_access_is_explicit_and_not_inherited_by_agents() {
let ceo = PrincipalRef::human_user("ceo-user");
let executive_grant = ScopedGrant::new(
"grant-ceo-org-read",
ceo.clone(),
ResourceRef::new("acme", "*", ResourceKind::Organization, "acme"),
GrantSource::ExecutiveGlobal,
)
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![
DataClass::Executive,
DataClass::FinancialRecord,
DataClass::SourceCode,
]);
let ceo_context = test_strict_context(
"*",
ceo,
ResourceScope::root(ResourceRef::new(
"acme",
"*",
ResourceKind::Organization,
"acme",
)),
vec![executive_grant],
);
let hr_compensation =
ResourceRef::new("acme", "hr", ResourceKind::Document, "compensation-bands");
let engineering_repo = ResourceRef::new(
"acme",
"engineering",
ResourceKind::Repository,
"product-api",
);
assert_eq!(
ceo_context
.evaluate_access(
&hr_compensation,
AccessPermission::Read,
DataClass::FinancialRecord,
1_500,
)
.decision,
AccessDecision::Allow
);
assert_eq!(
ceo_context
.evaluate_access(
&engineering_repo,
AccessPermission::Read,
DataClass::SourceCode,
1_500,
)
.decision,
AccessDecision::Allow
);
let ceo_agent =
PrincipalRef::agent_worker("agent-ceo-summary").with_tenant_actor_id("ceo-user");
let narrow_agent_grant = ScopedGrant::new(
"grant-agent-product-read",
ceo_agent.clone(),
ResourceRef::new("acme", "engineering", ResourceKind::Project, "product-api"),
GrantSource::Delegation,
)
.with_source_principal(PrincipalRef::human_user("ceo-user"))
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::SourceCode]);
let agent_context = test_strict_context(
"engineering",
ceo_agent.clone(),
ResourceScope::root(ResourceRef::new(
"acme",
"engineering",
ResourceKind::Project,
"product-api",
)),
vec![narrow_agent_grant],
);
let agent_denied_hr = agent_context.evaluate_access(
&hr_compensation,
AccessPermission::Read,
DataClass::FinancialRecord,
1_500,
);
assert_eq!(agent_denied_hr.decision, AccessDecision::NotApplicable);
assert_eq!(agent_denied_hr.reason, "resource_outside_projected_scope");
let projected_agent_grant = ScopedGrant::new(
"grant-agent-executive-projection",
ceo_agent.clone(),
ResourceRef::new("acme", "*", ResourceKind::Organization, "acme"),
GrantSource::Delegation,
)
.with_source_principal(PrincipalRef::human_user("ceo-user"))
.with_permissions(vec![AccessPermission::Read])
.with_data_classes(vec![DataClass::FinancialRecord])
.with_delegation_id("delegation-ceo-summary");
let projected_agent_context = test_strict_context(
"*",
ceo_agent,
ResourceScope::root(ResourceRef::new(
"acme",
"*",
ResourceKind::Organization,
"acme",
)),
vec![projected_agent_grant],
);
assert_eq!(
projected_agent_context
.evaluate_access(
&hr_compensation,
AccessPermission::Read,
DataClass::FinancialRecord,
1_500,
)
.decision,
AccessDecision::Allow
);
}
#[test]
fn connector_credential_ref_defaults_to_read_only_secret_reference() {
let tenant = TenantContext::explicit_user_workspace(
"acme",
"finance",
Some("deployment-prod".to_string()),
"user-admin",
);
let credential = ConnectorCredentialRef::read_only(
"acme",
"finance",
"google-drive-finance",
"credential-readonly",
SecretRef {
org_id: "acme".to_string(),
workspace_id: "finance".to_string(),
provider: "google_kms".to_string(),
secret_id: "secret://connectors/google-drive-finance/read".to_string(),
name: "Google Drive read token".to_string(),
},
1_000,
)
.with_source_bound_resource(ResourceRef::new(
"acme",
"finance",
ResourceKind::SharedDrive,
"finance-drive",
));
assert_eq!(
credential.credential_class,
ConnectorCredentialClass::ReadOnly
);
assert!(credential.validate_for_tenant(&tenant).is_ok());
let encoded = serde_json::to_value(&credential).expect("serialize credential ref");
assert_eq!(encoded["credential_class"], "read_only");
assert_eq!(
encoded["secret_ref"]["secret_id"],
credential.secret_ref.secret_id
);
assert!(encoded.get("credential_value").is_none());
assert!(encoded.get("access_token").is_none());
assert_eq!(
encoded["source_bound_resource"]["resource_kind"],
"shared_drive"
);
let wrong_tenant = TenantContext::explicit_user_workspace(
"acme",
"engineering",
Some("deployment-prod".to_string()),
"user-admin",
);
assert!(matches!(
credential.validate_for_tenant(&wrong_tenant),
Err(SecretRefError::WorkspaceMismatch)
));
}
#[test]
fn source_binding_blocks_ingestion_when_connector_or_binding_is_not_active() {
let tenant = TenantContext::explicit_user_workspace(
"acme",
"finance",
Some("deployment-prod".to_string()),
"user-admin",
);
let admin = PrincipalRef::human_user("user-admin");
let connector = ConnectorInstance::active(
"google-drive-finance",
tenant.clone(),
"google_drive",
admin.clone(),
1_000,
);
let binding = SourceBinding::enabled(
"binding-finance-drive",
tenant.clone(),
"google-drive-finance",
"google_drive_shared_drive",
"drive-finance",
ResourceRef::new("acme", "finance", ResourceKind::DataStore, "finance-docs"),
DataClass::FinancialRecord,
admin,
1_000,
);
assert!(binding.can_ingest_with(&connector));
let paused_connector = connector
.clone()
.with_state(ConnectorLifecycleState::Paused, 1_100);
assert!(!binding.can_ingest_with(&paused_connector));
let revoked_connector = connector
.clone()
.with_state(ConnectorLifecycleState::Revoked, 1_200);
assert!(!binding.can_ingest_with(&revoked_connector));
let quarantined_connector = connector
.clone()
.with_state(ConnectorLifecycleState::Quarantined, 1_300);
assert!(!binding.can_ingest_with(&quarantined_connector));
let disabled_binding = binding
.clone()
.with_state(SourceBindingState::Disabled, 1_400);
assert!(!disabled_binding.can_ingest_with(&connector));
let review_only_binding = binding.with_ingestion_policy(IngestionPolicy {
allow_indexing: false,
allow_prompt_context: false,
require_review: true,
max_depth: Some(2),
});
assert!(!review_only_binding.can_ingest_with(&connector));
}
#[test]
fn source_objects_and_memory_chunks_carry_resource_and_data_class_scope() {
let tenant = TenantContext::explicit_user_workspace(
"acme",
"finance",
Some("deployment-prod".to_string()),
"user-admin",
);
let resource = ResourceRef::new("acme", "finance", ResourceKind::Document, "board-report")
.with_parent_path(vec![ResourcePathSegment::new(
ResourceKind::SharedDrive,
"finance-drive",
)]);
let object = SourceObject {
source_object_id: "source-object-1".to_string(),
tenant_context: tenant.clone(),
binding_id: "binding-finance-drive".to_string(),
connector_id: "google-drive-finance".to_string(),
native_object_id: "drive-file-123".to_string(),
resource_ref: resource.clone(),
data_class: DataClass::FinancialRecord,
lifecycle_state: SourceObjectLifecycleState::Active,
native_object_path: Some("/finance/board-report.md".to_string()),
content_hash: Some("content-sha256:abc".to_string()),
source_hash: Some("sha256:abc".to_string()),
parent_source_object_id: None,
created_at_ms: 1_000,
updated_at_ms: 1_000,
last_seen_at_ms: Some(1_000),
lifecycle_changed_at_ms: None,
superseded_by_source_object_id: None,
};
let chunk = ScopedMemoryChunkRef {
chunk_id: "chunk-1".to_string(),
tenant_context: tenant.clone(),
source_object_id: object.source_object_id.clone(),
resource_ref: resource,
data_class: object.data_class,
source_hash: object.source_hash.clone(),
};
assert!(object.dedupe_scope_key().contains("acme:finance"));
assert!(object.dedupe_scope_key().contains("binding-finance-drive"));
assert!(object.tenant_matches(&tenant));
assert!(object.is_active());
assert!(object.allows_prompt_context());
assert!(object
.lifecycle_identity_key()
.contains("binding-finance-drive"));
assert!(!object
.clone()
.with_lifecycle_state(SourceObjectLifecycleState::Tombstoned, 2_000)
.allows_prompt_context());
assert_eq!(chunk.tenant_context, tenant);
assert_eq!(chunk.source_object_id, "source-object-1");
assert_eq!(chunk.data_class, DataClass::FinancialRecord);
let encoded = serde_json::to_value(&chunk).expect("serialize memory chunk ref");
assert_eq!(encoded["source_object_id"], "source-object-1");
assert_eq!(encoded["resource_ref"]["resource_kind"], "document");
assert_eq!(encoded["data_class"], "financial_record");
}
#[test]
fn ingestion_quarantine_tracks_review_without_making_output_searchable() {
let tenant = TenantContext::explicit_user_workspace(
"acme",
"legal",
Some("deployment-prod".to_string()),
"user-legal",
);
let quarantine = IngestionQuarantine {
quarantine_id: "quarantine-1".to_string(),
tenant_context: tenant,
connector_id: "notion-legal".to_string(),
binding_id: "binding-legal-notion".to_string(),
source_object_ids: vec!["source-object-legal-1".to_string()],
reason: "high_risk_data_class_requires_review".to_string(),
created_at_ms: 1_000,
reviewed_by: Some(PrincipalRef::human_user("legal-admin")),
reviewed_at_ms: Some(1_500),
disposition: Some(QuarantineDisposition::Delete),
};
let job = IngestionJob {
job_id: "ingestion-job-1".to_string(),
tenant_context: quarantine.tenant_context.clone(),
connector_id: quarantine.connector_id.clone(),
binding_id: quarantine.binding_id.clone(),
state: IngestionJobState::Quarantined,
source_object_ids: quarantine.source_object_ids.clone(),
started_at_ms: Some(900),
finished_at_ms: Some(1_000),
quarantine_id: Some(quarantine.quarantine_id.clone()),
};
assert_eq!(job.state, IngestionJobState::Quarantined);
assert_eq!(job.quarantine_id.as_deref(), Some("quarantine-1"));
assert_eq!(quarantine.disposition, Some(QuarantineDisposition::Delete));
let encoded = serde_json::to_value(&quarantine).expect("serialize quarantine");
assert_eq!(encoded["disposition"], "delete");
assert_eq!(encoded["reason"], "high_risk_data_class_requires_review");
}
fn test_strict_context(
workspace_id: &str,
principal: PrincipalRef,
resource_scope: ResourceScope,
grants: Vec<ScopedGrant>,
) -> StrictTenantContext {
StrictTenantContext::new(
TenantContext::explicit_user_workspace(
"acme",
workspace_id,
Some("deployment-test".to_string()),
principal.id.clone(),
),
principal,
AuthorityChain::from_request(RequestPrincipal::authenticated_user(
"user-test",
"tandem-web",
)),
resource_scope,
AssertionMetadata::new(
"tandem-web",
"tandem-runtime",
1_000,
2_000,
"assertion-test",
),
)
.with_grants(grants)
.with_data_boundary(DataBoundary::allow(vec![
DataClass::Internal,
DataClass::Confidential,
DataClass::Executive,
DataClass::FinancialRecord,
DataClass::SourceCode,
]))
}
}