tidaldb/tidal/tests/m9p1_scopes.rs
jx12n b55ad70141 fix: M0-M10 third-pass remediation — durability, replication, and CLI hardening
Resolves the 142 findings from tidal/docs/reviews/CODE_REVIEW_m0-m10.md across
the engine, server, net, and CLI surfaces:

- WAL/session-journal durability, checkpoint format, and crash-recovery hardening
- Replication shipper/receiver, tenant isolation, and migration paths
- Cluster scatter-gather, router, standalone server + health/offload endpoints
- tidalctl refactored into command modules with JSON output and WAL-state tooling
- Cohort, governance, signal-ledger, and vector-registry correctness fixes
- Expanded UAT/integration/durability test coverage across all milestones
2026-06-08 10:28:34 -06:00

186 lines
5.4 KiB
Rust

//! M9p1 integration test — Signal Scope & Share Contract.
//!
//! Validates the public `signal_scoped` API end-to-end through a real WAL and
//! the load-bearing M9 invariant: community/session/agent-scoped writes are
//! durably logged but never perturb the *local* profile.
//!
//! ROADMAP §Milestone 9 Phase 1 acceptance:
//! - Default behavior is local-only; sharing is explicit opt-in.
//! - The local profile remains intact across scoped writes.
//! - Share eligibility is decided by `SharePolicy` (per-intent).
#![allow(clippy::too_many_lines, clippy::unwrap_used)]
use std::{collections::BTreeSet, time::Duration};
use tidaldb::{
CommunityId, ShareIntent, ShareMode, SharePolicy, SignalScope, TidalDb,
schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window},
};
fn schema() -> tidaldb::schema::Schema {
let mut b = SchemaBuilder::new();
let _ = b
.signal(
"view",
EntityKind::Item,
DecaySpec::Exponential {
half_life: Duration::from_secs(7 * 24 * 3600),
},
)
.windows(&[Window::AllTime])
.velocity(false)
.add();
let _ = b
.signal(
"low_quality",
EntityKind::Item,
DecaySpec::Exponential {
half_life: Duration::from_secs(24 * 3600),
},
)
.windows(&[Window::AllTime])
.velocity(false)
.add();
b.build().unwrap()
}
#[test]
fn default_signal_is_local() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let id = EntityId::new(1);
db.signal("view", id, 1.0, Timestamp::now()).unwrap();
// A plain `signal` is local — it shows up in the local profile.
assert!(
db.read_decay_score(id, "view", 0)
.unwrap()
.is_some_and(|s| s > 0.0)
);
}
#[test]
fn community_scope_does_not_touch_local_profile() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let id = EntityId::new(2);
// A community-scoped write is accepted and durably logged...
db.signal_scoped(
"view",
id,
1.0,
Timestamp::now(),
SignalScope::Community(CommunityId(1)),
1,
)
.unwrap();
// ...but the local profile is untouched (local-profile-intact invariant).
assert!(
db.read_decay_score(id, "view", 0).unwrap().is_none(),
"community write must not create a local aggregate"
);
assert_eq!(
db.read_windowed_count(id, "view", Window::AllTime).unwrap(),
0
);
}
#[test]
fn local_and_community_writes_are_independent() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let id = EntityId::new(3);
let now = Timestamp::now();
// Interleave a local and a community write on the same entity.
db.signal_scoped("view", id, 1.0, now, SignalScope::Local, 0)
.unwrap();
db.signal_scoped(
"view",
id,
1.0,
now,
SignalScope::Community(CommunityId(9)),
1,
)
.unwrap();
// Only the local write affects the local windowed count.
assert_eq!(
db.read_windowed_count(id, "view", Window::AllTime).unwrap(),
1,
"exactly one (the local) write must count toward the local profile"
);
}
#[test]
fn share_policy_decides_per_intent_eligibility() {
// The SharePolicy is the per-intent gate that a community-sync layer (M9p2)
// consults before writing a community-scoped signal. Verify its contract
// directly: `low_quality` is allowed to ship, `view` is not.
let policy = SharePolicy {
version: 1,
allowed_intents: BTreeSet::from([ShareIntent::LowQuality]),
mode: ShareMode::CommunityShare,
};
let scope = SignalScope::Community(CommunityId(1));
assert!(policy.is_signal_share_eligible(scope, "low_quality"));
assert!(!policy.is_signal_share_eligible(scope, "view"));
// Local scope is never eligible, even for an allowed intent.
assert!(!policy.is_signal_share_eligible(SignalScope::Local, "low_quality"));
}
#[test]
fn scoped_writes_survive_reopen_without_polluting_local() {
// Durability: a community write is in the WAL and replays on reopen, but
// still must not appear in the recovered local profile.
let home = tidaldb::TempTidalHome::new().unwrap();
let id = EntityId::new(42);
let now = Timestamp::now();
{
let db = TidalDb::builder()
.with_data_dir(home.path())
.with_schema(schema())
.open()
.unwrap();
db.signal_scoped("view", id, 1.0, now, SignalScope::Local, 0)
.unwrap();
db.signal_scoped(
"view",
id,
1.0,
now,
SignalScope::Community(CommunityId(1)),
1,
)
.unwrap();
db.shutdown().unwrap();
}
// Reopen and replay the WAL.
let db = TidalDb::builder()
.with_data_dir(home.path())
.with_schema(schema())
.open()
.unwrap();
// Exactly the one local write is reflected in the recovered local profile.
assert_eq!(
db.read_windowed_count(id, "view", Window::AllTime).unwrap(),
1,
"after replay, only the local write should affect the local profile"
);
}