tidaldb/tidal/tests/m10p3_provenance.rs
jx12n 3bcfb3c576 feat: Bazel build, crate docs/ai-lookup, docker images, and engine hardening
- Add BUILD.bazel across tidal, tidal-net, tidal-server, tidalctl for bzlmod build
- Add tidal/ crate docs (README, CHANGELOG, CONTRIBUTING, AGENTS, CLAUDE, API, ARCHITECTURE) and ai-lookup reference
- Add docker standalone/cluster/deploy images, compose, and prometheus config
- Harden WAL (batch format, writer, dedup, diagnostics), text syncer/collectors, and vector registry
- Expand tidalctl CLI and tests; restructure WAL/visibility integration test suites
- Refine tidal-net transport/client/server and tidal-server cluster/scatter-gather
2026-06-07 18:29:38 -06:00

265 lines
8.2 KiB
Rust

//! M10p3 integration test — Provenance, Explainability, Remove-by-Scope.
//!
//! Validates that every community contribution carries provenance (user, writer
//! agent, epoch), that an aggregate can be explained as the contributions that
//! compose it, and that contributions can be removed precisely by user or by
//! agent — non-globally, deterministically, with the local profile intact and
//! the removal surviving reopen. Exercises the M10 UAT: revoke `A_trusted`, then
//! remove `A_trusted`'s prior contributions.
//!
//! ROADMAP §Milestone 10 Phase 3.
#![allow(clippy::unwrap_used)]
use std::time::Duration;
use tidaldb::{
CommunityId, RemoveScope, ScopeClass, ScopePermission, ShareIntent, SharePolicy, TidalDb,
UserId,
schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window},
};
fn schema() -> tidaldb::schema::Schema {
let mut b = SchemaBuilder::new();
let _ = b
.signal(
"like",
EntityKind::Item,
DecaySpec::Exponential {
half_life: Duration::from_secs(7 * 24 * 3600),
},
)
.windows(&[Window::AllTime])
.velocity(false)
.add();
b.build().unwrap()
}
fn share() -> SharePolicy {
SharePolicy::community_share(1, [ShareIntent::Like])
}
fn grant_writer(db: &TidalDb, agent: &str) {
db.grant_capability(
agent,
vec![ScopePermission::read_write(ScopeClass::Community)],
None,
)
.unwrap();
}
#[test]
fn contributions_carry_provenance_and_explain() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let (c, item) = (CommunityId(1), EntityId::new(5));
db.join_community(UserId(1), c, share()).unwrap();
grant_writer(&db, "A_trusted");
let now = Timestamp::now();
// A direct user write + an agent-attributed write on the same item.
db.signal_for_community("like", item, 1.0, now, UserId(1), c)
.unwrap();
db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, UserId(1), c)
.unwrap();
let provenance = db.explain_community_contributions(c, item, "like").unwrap();
assert_eq!(
provenance.len(),
2,
"two distinct contributors (direct + agent)"
);
// Direct write has empty writer; agent write is attributed to A_trusted.
assert!(
provenance
.iter()
.any(|p| p.user == UserId(1) && p.writer_agent.is_empty())
);
assert!(
provenance
.iter()
.any(|p| p.user == UserId(1) && p.writer_agent == "A_trusted")
);
}
#[test]
fn remove_by_agent_is_precise() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
db.join_community(user, c, share()).unwrap();
grant_writer(&db, "A_trusted");
grant_writer(&db, "A_other");
let now = Timestamp::now();
db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
.unwrap();
db.agent_signal_for_community("A_other", "like", item, 1.0, now, user, c)
.unwrap();
db.signal_for_community("like", item, 1.0, now, user, c)
.unwrap(); // direct
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
3
);
// Remove ONLY A_trusted's contributions.
let receipt = db
.remove_from_personalization(RemoveScope::CommunityAgent {
writer_agent: "A_trusted".to_string(),
community: c,
})
.unwrap();
assert_eq!(receipt.contributions_removed, 1);
// A_other's + the direct contribution remain (precise, non-global).
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
2
);
let remaining = db.explain_community_contributions(c, item, "like").unwrap();
assert!(!remaining.iter().any(|p| p.writer_agent == "A_trusted"));
assert!(remaining.iter().any(|p| p.writer_agent == "A_other"));
}
#[test]
fn m10_uat_revoke_and_remove_agent() {
// The ROADMAP M10 UAT step: U revokes A_trusted community scope and removes
// A_trusted's prior contributions from C; the local profile is untouched.
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
db.join_community(user, c, share()).unwrap();
let token = db
.grant_capability(
"A_trusted",
vec![ScopePermission::read_write(ScopeClass::Community)],
None,
)
.unwrap();
let now = Timestamp::now();
db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
.unwrap();
db.signal("like", item, 1.0, now).unwrap(); // U's own local like
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
1
);
let local_before = db
.read_windowed_count(item, "like", Window::AllTime)
.unwrap();
// 1. Revoke A_trusted's community scope -> future writes denied immediately.
db.revoke_capability(&token).unwrap();
assert!(
db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
.is_err()
);
// 2. Remove A_trusted's prior contributions from C.
db.remove_from_personalization(RemoveScope::CommunityAgent {
writer_agent: "A_trusted".to_string(),
community: c,
})
.unwrap();
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
0
);
// 3. U's local profile is completely intact.
assert_eq!(
db.read_windowed_count(item, "like", Window::AllTime)
.unwrap(),
local_before
);
}
#[test]
fn remove_by_user_scope_delegates_to_purge() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let (c, item) = (CommunityId(1), EntityId::new(5));
let now = Timestamp::now();
for u in 1..=3u64 {
db.join_community(UserId(u), c, share()).unwrap();
db.signal_for_community("like", item, 1.0, now, UserId(u), c)
.unwrap();
}
let receipt = db
.remove_from_personalization(RemoveScope::CommunityUser {
user: UserId(2),
community: c,
epoch_range: 0..=u32::MAX,
})
.unwrap();
assert_eq!(receipt.contributions_removed, 1);
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
2
);
}
#[test]
fn remove_survives_reopen() {
let home = tidaldb::TempTidalHome::new().unwrap();
let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
{
let db = TidalDb::builder()
.with_data_dir(home.path())
.with_schema(schema())
.open()
.unwrap();
db.join_community(user, c, share()).unwrap();
grant_writer(&db, "A_trusted");
let now = Timestamp::now();
db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
.unwrap();
db.signal_for_community("like", item, 1.0, now, user, c)
.unwrap();
db.remove_from_personalization(RemoveScope::CommunityAgent {
writer_agent: "A_trusted".to_string(),
community: c,
})
.unwrap();
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
1
);
db.shutdown().unwrap();
}
// Reopen: the remove-by-agent outcome is preserved (the agent's contribution
// stays removed, the direct one stays).
let db = TidalDb::builder()
.with_data_dir(home.path())
.with_schema(schema())
.open()
.unwrap();
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
1
);
let prov = db.explain_community_contributions(c, item, "like").unwrap();
assert!(!prov.iter().any(|p| p.writer_agent == "A_trusted"));
}