3.6 KiB
3.6 KiB
Tasks: Retroactive Signal Purge
T1: CohortContributionLog
File: tidal/src/cohort/contribution.rs (new)
Implement CohortContributionLog with bounded Mutex<VecDeque<ContributionRecord>>.
ContributionRecord { user_id: u64, cohort: String, entity_id: EntityId, signal_type_id: SignalTypeId, weight: f32, timestamp_ns: u64 }DEFAULT_CONTRIBUTION_LOG_CAP: usize = 5_000_000CohortContributionLog::new()and::with_cap(cap)push(record)— evicts oldest if at cap; incrementseviction_countdrain_for(user_id, cohort) -> Vec<ContributionRecord>len(),is_empty(),eviction_count(),cap()- Unit tests: push/drain, eviction, multi-user isolation
Status: DONE
T2: Ledger Retraction Methods
Files: tidal/src/signals/hot.rs, tidal/src/signals/warm.rs, tidal/src/cohort/ledger.rs
HotSignalState::subtract_contribution(weight, ts_ns, lambdas)— CAS loop, floor at 0.0BucketedCounter::subtract_bucket(ts_ns)— decrement all_time + current buckets, floor 0CohortSignalLedger::retract(cohort, entity_id, type_id, weight, ts_ns)— silent no-op if entry absentCohortSignalLedger::lambdas_for(type_id) -> &[f64]CohortSignalLedger::remove_entry(cohort, entity_id, type_id)
Status: DONE
T3: Tag::PurgeManifest Storage Key
File: tidal/src/storage/keys.rs
- Add
PurgeManifest = 0x10variant toTagenum - Add
0x10 => Some(Self::PurgeManifest)tofrom_byte() - Update all tag test arrays to include new variant
Status: DONE
T4: PurgeCoordinator and PurgeManifest
File: tidal/src/cohort/purge.rs (rewritten from stub)
PurgeId = u128ManifestEntry(serde surrogate)PurgeManifest { purge_id, user_id, cohort, requested_at_ns, entries, evicted_before_purge }withto_json()/from_json()PurgeCoordinator::request_purge(user_id, cohort, now_ns, evicted) -> (PurgeId, PurgeManifest)- Unit tests: drain/retract/manifest, idempotency, JSON roundtrip
Status: DONE
T5: TidalDb Struct Fields
File: tidal/src/db/mod.rs
- Add
contribution_log: Arc<CohortContributionLog>andpurge_coordinator: Arc<PurgeCoordinator>toTidalDb - Initialize in both
from_config(no-schema stubs) and schema-open path - Add
purge_job_queue,rematerialization_metrics,rematerialization_handlefor M9 re-mat engine
Status: DONE
T6: Wire Contribution Log into Signal Hot Path
File: tidal/src/db/signals.rs
In try_cohort_attribution, after each cohort_ledger.record(...):
self.contribution_log.push(ContributionRecord {
user_id, cohort: cohort_name.clone(), entity_id,
signal_type_id: type_id, weight: weight as f32, timestamp_ns: ts_ns,
});
Status: DONE
T7: Public API db/purge.rs
File: tidal/src/db/purge.rs (new)
TidalDb::request_community_purge(user_id, cohort_name) -> Result<(PurgeId, PurgeManifest)>require_writeable()purge_coordinator.request_purge(...)→ in-memory retractionstorage.items_engine().put(encode_key(...), manifest.to_json())for durability
TidalDb::list_purge_manifests(user_id) -> Result<Vec<PurgeManifest>>- Prefix scan on
Tag::PurgeManifest
- Prefix scan on
Status: DONE
T8: Integration Tests
File: tidal/tests/m9_retroactive_purge.rs (new)
8 tests covering:
- Basic purge retracts cohort score
- Second purge is idempotent (empty manifest, no double-retract)
- Purging user A doesn't affect user B's contributions
- Manifest persisted and listable in ephemeral mode
- Purging unknown cohort returns empty manifest (no error)
- Score floors at 0.0, never goes negative
- Multiple items purged in one request
- Non-cohort-member purge is a no-op
Status: DONE