tandem-memory 0.6.0

Memory storage and embedding utilities for Tandem
Documentation
use crate::types::{GlobalMemoryRecord, MemoryAccessFilter, MemorySourceAccessTarget};
use tandem_enterprise_contract::{
    AccessPermission, AssertionMetadata, AuthorityChain, CrossTenantGrant, CrossTenantGrantClaims,
    CrossTenantGrantHeader, CrossTenantGrantParty, CrossTenantGrantRecord, DataBoundary, DataClass,
    GrantSource, PrincipalRef, RequestPrincipal, ResourceKind, ResourceRef, ResourceScope,
    ScopedGrant, StrictTenantContext, TenantContext,
};

#[test]
fn memory_access_filter_allows_active_cross_tenant_projection() {
    let issuer = TenantContext::explicit_user_workspace("org-a", "workspace-a", None, "admin-a");
    let audience = TenantContext::explicit_user_workspace("org-b", "workspace-b", None, "user-b");
    let subject = PrincipalRef::human_user("user-b");
    let resource = ResourceRef::new(
        "org-a",
        "workspace-a",
        ResourceKind::DocumentCollection,
        "finance-drive",
    );
    let claims = CrossTenantGrantClaims::new_v1(
        "grant-finance",
        CrossTenantGrantParty::from_tenant_context(&issuer),
        CrossTenantGrantParty::from_tenant_context(&audience),
        subject.clone(),
        ResourceScope::root(resource.clone()),
        vec![AccessPermission::Read],
        vec![DataClass::FinancialRecord],
        1_000,
        5_000,
        PrincipalRef::human_user("admin-a"),
    );
    let record = CrossTenantGrantRecord::active(
        CrossTenantGrant::new(
            CrossTenantGrantHeader::ed25519("grant-key"),
            claims,
            "signature-bytes",
        ),
        1_000,
    );
    let request_principal = RequestPrincipal::authenticated_user("user-b", "test");
    let mut strict = StrictTenantContext::new(
        audience,
        subject,
        AuthorityChain::from_request(request_principal),
        ResourceScope::root(ResourceRef::new(
            "org-b",
            "workspace-b",
            ResourceKind::Workspace,
            "workspace-b",
        )),
        AssertionMetadata::new("issuer", "runtime", 1_000, 5_000, "assertion-b"),
    )
    .with_data_boundary(DataBoundary::allow(vec![DataClass::FinancialRecord]));

    assert!(record.project_into_strict_context(&mut strict, 2_000));
    let filter = MemoryAccessFilter::strict(strict, 2_000);
    let target = MemorySourceAccessTarget {
        resource_ref: resource,
        data_class: DataClass::FinancialRecord,
        source_binding_id: Some("finance-drive".to_string()),
        source_object_id: Some("statement-q4".to_string()),
    };

    assert!(filter.allows_source_target(&target));
}

#[test]
fn governed_read_filter_denies_missing_strict_projection() {
    let filter = MemoryAccessFilter::governed(None, 2_000);
    let decision = filter.decision_for_global_record(&global_record(None));

    assert!(!decision.allowed);
    assert_eq!(
        decision.reason.as_deref(),
        Some("missing_strict_projection")
    );
}

#[test]
fn local_noop_read_filter_preserves_legacy_visibility() {
    let filter = MemoryAccessFilter::local_noop(2_000);
    let decision = filter.decision_for_global_record(&global_record(Some(serde_json::json!({
        "memory_trust": {
            "label": "connector_sourced"
        }
    }))));

    assert!(decision.allowed);
    assert_eq!(decision.reason.as_deref(), Some("local_noop"));
}

#[test]
fn governed_read_filter_allows_internal_tenant_memory_with_default_boundary() {
    let filter = MemoryAccessFilter::strict(tenant_strict(DataBoundary::unrestricted()), 2_000);
    let decision = filter.decision_for_global_record(&global_record(None));

    assert!(decision.allowed);
    assert_eq!(
        decision.reason.as_deref(),
        Some("tenant_local_memory_allowed")
    );
}

#[test]
fn governed_read_filter_denies_restricted_memory_with_default_boundary() {
    let filter = MemoryAccessFilter::strict(tenant_strict(DataBoundary::unrestricted()), 2_000);
    let decision = filter.decision_for_global_record(&global_record(Some(serde_json::json!({
        "classification": "restricted"
    }))));

    assert!(!decision.allowed);
    assert_eq!(
        decision.reason.as_deref(),
        Some("data_class_denied_by_boundary")
    );
}

#[test]
fn governed_read_filter_allows_restricted_memory_with_explicit_boundary() {
    let filter = MemoryAccessFilter::strict(
        tenant_strict(DataBoundary::allow(vec![DataClass::Restricted])),
        2_000,
    );
    let decision = filter.decision_for_global_record(&global_record(Some(serde_json::json!({
        "classification": "restricted"
    }))));

    assert!(decision.allowed);
    assert_eq!(
        decision.reason.as_deref(),
        Some("tenant_local_memory_allowed")
    );
}

#[test]
fn governed_read_filter_derives_boundary_from_allow_grants() {
    let resource = ResourceRef::new(
        "org-a",
        "workspace-a",
        ResourceKind::DocumentCollection,
        "finance-drive",
    );
    let grant = ScopedGrant::new(
        "grant-finance",
        PrincipalRef::human_user("user-a"),
        resource.clone(),
        GrantSource::Direct,
    )
    .with_permissions(vec![AccessPermission::Read])
    .with_data_classes(vec![DataClass::FinancialRecord]);
    let strict = tenant_strict(DataBoundary::unrestricted()).with_grants(vec![grant]);
    let filter = MemoryAccessFilter::strict(strict, 2_000);
    let target = MemorySourceAccessTarget {
        resource_ref: resource,
        data_class: DataClass::FinancialRecord,
        source_binding_id: Some("finance-drive".to_string()),
        source_object_id: Some("statement-q4".to_string()),
    };

    assert!(filter.allows_source_target(&target));
}

#[test]
fn governed_read_filter_ignores_expired_grants_when_deriving_boundary() {
    let grant = ScopedGrant::new(
        "grant-finance-expired",
        PrincipalRef::human_user("user-a"),
        ResourceRef::new(
            "org-a",
            "workspace-a",
            ResourceKind::Workspace,
            "workspace-a",
        ),
        GrantSource::Direct,
    )
    .with_permissions(vec![AccessPermission::Read])
    .with_data_classes(vec![DataClass::FinancialRecord])
    .with_expires_at_ms(1_500);
    let strict = tenant_strict(DataBoundary::unrestricted()).with_grants(vec![grant]);
    let filter = MemoryAccessFilter::strict(strict, 2_000);
    let decision = filter.decision_for_global_record(&global_record(Some(serde_json::json!({
        "classification": "financial_record"
    }))));

    assert!(!decision.allowed);
    assert_eq!(
        decision.reason.as_deref(),
        Some("data_class_denied_by_boundary")
    );
}

#[test]
fn governed_read_filter_denies_connector_sourced_memory_without_resource_metadata() {
    let filter = MemoryAccessFilter::strict(tenant_strict(DataBoundary::unrestricted()), 2_000);
    let decision = filter.decision_for_global_record(&global_record(Some(serde_json::json!({
        "memory_trust": {
            "label": "connector_sourced"
        }
    }))));

    assert!(!decision.allowed);
    assert_eq!(decision.reason.as_deref(), Some("missing_resource_ref"));
}

#[test]
fn governed_read_filter_denies_source_binding_without_data_class() {
    let filter = MemoryAccessFilter::strict(tenant_strict(DataBoundary::unrestricted()), 2_000);
    let decision = filter.decision_for_global_record(&global_record(Some(serde_json::json!({
        "enterprise_source_binding": {
            "binding_id": "finance-drive",
            "connector_id": "manual-upload",
            "resource_ref": {
                "organization_id": "org-a",
                "workspace_id": "workspace-a",
                "resource_kind": "document_collection",
                "resource_id": "finance-drive"
            }
        }
    }))));

    assert!(!decision.allowed);
    assert_eq!(decision.reason.as_deref(), Some("missing_data_class"));
}

fn tenant_strict(data_boundary: DataBoundary) -> StrictTenantContext {
    let tenant = TenantContext::explicit_user_workspace("org-a", "workspace-a", None, "user-a");
    let principal = RequestPrincipal::authenticated_user("user-a", "test");
    StrictTenantContext::new(
        tenant,
        PrincipalRef::human_user("user-a"),
        AuthorityChain::from_request(principal),
        ResourceScope::root(ResourceRef::new(
            "org-a",
            "workspace-a",
            ResourceKind::Workspace,
            "workspace-a",
        )),
        AssertionMetadata::new("issuer", "runtime", 1_000, 5_000, "assertion-a"),
    )
    .with_data_boundary(data_boundary)
}

fn global_record(metadata: Option<serde_json::Value>) -> GlobalMemoryRecord {
    GlobalMemoryRecord {
        id: "memory-a".to_string(),
        user_id: "user-a".to_string(),
        source_type: "note".to_string(),
        content: "tenant memory".to_string(),
        content_hash: "hash-a".to_string(),
        run_id: "run-a".to_string(),
        session_id: Some("session-a".to_string()),
        message_id: None,
        tool_name: None,
        project_tag: Some("project-a".to_string()),
        channel_tag: None,
        host_tag: None,
        metadata,
        provenance: None,
        redaction_status: "passed".to_string(),
        redaction_count: 0,
        visibility: "shared".to_string(),
        demoted: false,
        score_boost: 0.0,
        created_at_ms: 1_000,
        updated_at_ms: 1_000,
        expires_at_ms: None,
    }
}