tidaldb/tidal/tests/m10p1_governance.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

206 lines
6.1 KiB
Rust

//! M10p1 integration test — Community Governance Policy Engine.
//!
//! Validates versioned community policies governing signal eligibility and
//! per-contribution weighting: excluded intents are rejected, admitted weights
//! are clamped, versions are monotonic with effective timestamps, the governing
//! version is queryable, and policies survive close/reopen.
//!
//! ROADMAP §Milestone 10 Phase 1.
#![allow(clippy::unwrap_used)]
use std::{collections::BTreeSet, time::Duration};
use tidaldb::{
CommunityId, GovernancePolicy, ShareIntent, SharePolicy, TidalDb, UserId, WeightingBounds,
schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window},
};
fn schema() -> tidaldb::schema::Schema {
let mut b = SchemaBuilder::new();
for name in ["like", "low_quality", "skip_for_now"] {
let _ = b
.signal(
name,
EntityKind::Item,
DecaySpec::Exponential {
half_life: Duration::from_secs(7 * 24 * 3600),
},
)
.windows(&[Window::AllTime])
.velocity(false)
.add();
}
b.build().unwrap()
}
fn full_share() -> SharePolicy {
SharePolicy::community_share(
1,
[
ShareIntent::Like,
ShareIntent::LowQuality,
ShareIntent::SkipForNow,
],
)
}
fn policy(community: CommunityId, version: u32, effective_at_ns: u64) -> GovernancePolicy {
GovernancePolicy {
community_id: community,
version,
effective_at_ns,
allowed_intents: BTreeSet::new(),
excluded_intents: BTreeSet::from(["skip_for_now".to_string()]),
weighting: WeightingBounds { min: 0.0, max: 2.0 },
trust_threshold: 0.0,
}
}
#[test]
fn excluded_intent_is_rejected_by_policy() {
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, full_share()).unwrap();
db.define_community_policy(policy(c, 1, 0)).unwrap();
let now = Timestamp::now();
// `skip_for_now` is excluded by governance -> rejected (not recorded).
assert!(
!db.signal_for_community("skip_for_now", item, 1.0, now, user, c)
.unwrap()
);
// `like` is admitted.
assert!(
db.signal_for_community("like", item, 1.0, now, user, c)
.unwrap()
);
assert_eq!(
db.read_community_windowed_count(c, item, "like", Window::AllTime)
.unwrap(),
1
);
assert_eq!(
db.read_community_windowed_count(c, item, "skip_for_now", Window::AllTime)
.unwrap(),
0
);
}
#[test]
fn weight_is_clamped_by_policy() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let item = EntityId::new(5);
let now = Timestamp::now();
// Governed community: max weight 2.0.
let governed = CommunityId(1);
db.join_community(UserId(1), governed, full_share())
.unwrap();
db.define_community_policy(policy(governed, 1, 0)).unwrap();
assert!(
db.signal_for_community("like", item, 100.0, now, UserId(1), governed)
.unwrap()
);
// Ungoverned community: weight passes through.
let free = CommunityId(2);
db.join_community(UserId(1), free, full_share()).unwrap();
assert!(
db.signal_for_community("like", item, 100.0, now, UserId(1), free)
.unwrap()
);
let governed_score = db
.read_community_decay_score(governed, item, "like", 0)
.unwrap()
.unwrap();
let free_score = db
.read_community_decay_score(free, item, "like", 0)
.unwrap()
.unwrap();
assert!(
governed_score <= 2.0 + 1e-6,
"governed weight must be clamped to 2.0, got {governed_score}"
);
assert!(
free_score > 2.0,
"ungoverned weight must pass through, got {free_score}"
);
}
#[test]
fn policy_versions_are_monotonic_and_queryable() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let c = CommunityId(1);
db.define_community_policy(policy(c, 1, 0)).unwrap();
assert_eq!(db.governing_policy_version(c), Some(1));
// v2 supersedes once registered (effective at 0 here).
db.define_community_policy(policy(c, 2, 0)).unwrap();
assert_eq!(db.governing_policy_version(c), Some(2));
// Re-registering a non-increasing version fails.
assert!(db.define_community_policy(policy(c, 2, 0)).is_err());
assert!(db.define_community_policy(policy(c, 1, 0)).is_err());
}
#[test]
fn no_policy_means_no_governance() {
let db = TidalDb::builder()
.ephemeral()
.with_schema(schema())
.open()
.unwrap();
let (c, item, user) = (CommunityId(9), EntityId::new(1), UserId(1));
db.join_community(user, c, full_share()).unwrap();
let now = Timestamp::now();
// Without a governance policy, even `skip_for_now` flows (share policy allows it).
assert!(
db.signal_for_community("skip_for_now", item, 1.0, now, user, c)
.unwrap()
);
assert_eq!(db.governing_policy_version(c), None);
}
#[test]
fn policy_survives_reopen() {
let home = tidaldb::TempTidalHome::new().unwrap();
let c = CommunityId(1);
{
let db = TidalDb::builder()
.with_data_dir(home.path())
.with_schema(schema())
.open()
.unwrap();
db.define_community_policy(policy(c, 1, 0)).unwrap();
db.define_community_policy(policy(c, 2, 0)).unwrap();
db.shutdown().unwrap();
}
let db = TidalDb::builder()
.with_data_dir(home.path())
.with_schema(schema())
.open()
.unwrap();
assert_eq!(
db.governing_policy_version(c),
Some(2),
"governance policy must survive reopen"
);
// Monotonic check still holds against restored history.
assert!(db.define_community_policy(policy(c, 2, 0)).is_err());
assert!(db.define_community_policy(policy(c, 3, 0)).is_ok());
}