# QA Plan: Retroactive Signal Purge ## Test Strategy Unit tests in the relevant modules validate individual components. Integration tests in `tidal/tests/m9_retroactive_purge.rs` validate the end-to-end purge flow via the public `TidalDb` API. ## Unit Test Cases ### QA-U1: Contribution log push and drain - Push 3 records for user A (cohort "x") and 2 for user B (cohort "x"). - `drain_for(user_A, "x")` returns 3 records and leaves 2 for user B. - After drain, log has 2 entries. ### QA-U2: Contribution log eviction - Create log with cap=3. - Push 5 records. - `eviction_count()` == 2. - Log length == 3 (last 3 entries). ### QA-U3: PurgeCoordinator drains and retracts - Seed cohort ledger with 2 view signals for entity 10. - Push matching contribution records. - `request_purge(user, "cohort")` → score decreases, manifest has 2 entries. ### QA-U4: Second purge is no-op on ledger - After first purge, contribution log is empty. - Second `request_purge` returns manifest with 0 entries. - Ledger score unchanged from first purge. ### QA-U5: Manifest JSON roundtrip - Build a `PurgeManifest` with known fields. - `to_json()` → `from_json()` restores all fields. ### QA-U6: Malformed JSON returns None - `PurgeManifest::from_json(b"")` == `None`. - `PurgeManifest::from_json(b"not json")` == `None`. ## Integration Test Cases ### QA-I1: `basic_purge_retracts_score` - Define cohort "en_users" (locale=en). - User 1 (en), signals item 100 twice. - `request_community_purge(1, "en_users")` → score decreases, manifest non-empty. ### QA-I2: `second_purge_is_idempotent` - User 2 signals item 200 once. - First purge: score decreases, manifest has 1 entry. - Second purge: manifest has 0 entries, score unchanged from after first purge. ### QA-I3: `purge_does_not_affect_other_users` - Users 3 and 4 both signal item 300. - Purge user 3 only. - Score still positive (user 4's contribution intact). ### QA-I4: `manifest_persisted_and_listable` - User 5 signals item 500, then purged. - `list_purge_manifests(5)` returns 1 manifest with correct purge_id. - Second purge creates second manifest; list returns 2. ### QA-I5: `purge_unknown_cohort_is_noop` - User 6 exists, no cohort defined. - `request_community_purge(6, "nonexistent")` succeeds with 0-entry manifest. ### QA-I6: `score_never_goes_negative` - User 7 signals once; purge applied. - `read_decay_score(...)` >= 0.0. ### QA-I7: `multiple_items_purged_together` - User 8 signals items 801 and 802. - `request_community_purge(8, "en_users")` → manifest has 2 entries; both scores decrease. ### QA-I8: `non_cohort_member_purge_is_noop` - User 9 (en) and user 10 (fr) signal item 900. - Only user 9 lands in cohort "en_users". - Purge user 10 from "en_users" → 0-entry manifest, score unchanged. ## Coverage Requirements - All 8 integration tests must pass. - All 6 unit tests in `cohort::purge::tests` must pass. - All contribution log unit tests in `cohort::contribution::tests` must pass. - `cargo test --lib` 1299+ tests passing. - `cargo fmt -- --check` clean. - `cargo build` clean (no errors, no relevant warnings).