# Design: Personalization Correctness Verification ## Overview This feature adds a comprehensive integration test suite (`tidal/tests/pg1_personalization_correctness.rs`) that mathematically verifies every link in the personalization loop. No production code changes are required — this is a pure verification feature. ## Architecture ``` pg1_personalization_correctness.rs ├── mod decay_tests │ ├── single_event_decay_matches_analytical │ ├── multi_event_decay_matches_analytical │ ├── out_of_order_converges_to_in_order │ └── decay_score_is_zero_after_many_half_lives ├── mod windowed_count_tests │ ├── one_hour_window_exact_count │ ├── velocity_equals_count_over_duration │ └── all_time_count_is_monotonic ├── mod preference_vector_tests │ ├── ema_update_matches_manual_computation │ ├── adaptive_learning_rate_decays │ └── dimension_mismatch_is_noop ├── mod ranking_reactivity_tests │ ├── signal_immediately_visible_in_retrieve │ └── signal_immediately_visible_in_search ├── mod score_ordering_tests │ ├── more_engagement_ranks_higher │ ├── interaction_boost_is_additive │ ├── boost_increases_score_linearly │ ├── penalty_decreases_score_linearly │ └── no_nan_or_infinity_in_scores ├── mod gate_tests │ ├── below_threshold_excluded │ └── at_threshold_included └── mod diversity_tests └── max_per_creator_enforced ``` ## Test Schema All tests share a common schema builder helper: ```rust fn test_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::OneHour, Window::TwentyFourHours, Window::AllTime]) .velocity(true) .add(); let _ = b.signal("like", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(14 * 24 * 3600) }) .windows(&[Window::AllTime]) .velocity(false) .add(); let _ = b.signal("share", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(3 * 24 * 3600) }) .windows(&[Window::TwentyFourHours, Window::AllTime]) .velocity(true) .add(); let _ = b.signal("completion", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(30 * 24 * 3600) }) .windows(&[Window::AllTime]) .velocity(false) .add(); b.build().unwrap() } ``` ## Analytical Reference Implementation The test file includes a pure-Rust analytical decay calculator used as the oracle: ```rust /// Compute expected decay score analytically. /// events: Vec<(timestamp_ns, weight)>, sorted chronologically. /// query_time_ns: the time at which the score is evaluated. /// lambda: decay rate (ln(2) / half_life_secs). fn analytical_decay_score(events: &[(u64, f64)], query_time_ns: u64, lambda: f64) -> f64 { events.iter().fold(0.0, |acc, &(ts, w)| { let dt = (query_time_ns.saturating_sub(ts)) as f64 / 1e9; acc + w * (-lambda * dt).exp() }) } ``` This differs from the incremental implementation (`HotSignalState::on_signal` + `current_score`) by computing from first principles — the two must agree within 1e-10 relative error. ## Reactivity Test Design ``` 1. Open ephemeral TidalDb with test schema + "for_you" profile 2. Write 10 items with metadata 3. Record view signals for items 1-5 (established baseline) 4. let t0 = Instant::now() 5. db.signal("like", item_1, 10.0, Timestamp::now()) // big boost 6. let results = db.retrieve(&Retrieve::builder() .profile("for_you") .for_user(user_1) .limit(10) .build()) 7. assert item_1 is in top 3 8. assert t0.elapsed() < Duration::from_millis(100) ``` The 100ms budget is generous — in-memory path is typically < 1ms. The budget accounts for worst-case WAL append and any scheduling jitter. ## Score Ordering Test Design For monotonicity verification: 1. Create two items A and B with identical metadata. 2. Record 10 views for A, 5 views for B. 3. Retrieve with `for_you` profile. 4. Assert A ranks above B (more views = higher score when all else is equal). For interaction boost: 1. Create items from creator_1 and creator_2. 2. Record interaction between user and creator_1. 3. Retrieve for user — items from creator_1 should rank higher than equivalent items from creator_2. ## Gate Test Design 1. Create profile with gate: `{ signal: "view", agg: Value, window: AllTime, min_threshold: 5.0 }`. 2. Write items: A with 10 views, B with 3 views, C with 5 views. 3. Retrieve — A and C should appear, B should not. ## Diversity Test Design 1. Create 25 items across 5 creators with varying engagement levels. 2. Use `trending` builtin profile which has max_per_creator=1. 3. Retrieve with limit 10. 4. Assert diversity is enforced: max per creator <= 5, at least 2 creators represented. ## Files Changed | File | Change | |------|--------| | `tidal/tests/pg1_personalization_correctness.rs` | New integration test file (all tests) | ## No Production Code Changes This feature is purely additive — new test file only. No changes to `tidal/src/` are required.