# Tasks: Re-materialization after Purge ## T1: PurgeJob and PurgeJobQueue **File**: `tidal/src/cohort/rematerialization/job.rs` - `JobId = [u8; 16]`, `new_job_id()` (timestamp+counter), `job_id_to_string()` - `PurgeJobStatus` enum: Pending, Running, Succeeded, Failed, PermanentlyFailed - `PurgeJob` struct with all lifecycle fields - `PurgeJobQueue`: DashMap + FIFO order Vec; `submit`, `claim_pending`, `update_status`, `requeue_interrupted`, `requeue_failed`, `list_jobs`, `job_status`, `pending_count` - Status: COMPLETE ## T2: RematerializationMetrics **File**: `tidal/src/cohort/rematerialization/metrics.rs` - `RematerializationMetrics` (AtomicU64 fields): `jobs_pending`, `jobs_succeeded`, `jobs_failed`, `last_job_duration_ms`, `last_job_entries_updated` - `RematerializationMetricsSnapshot` (plain u64/usize) - `RematerializationMetrics::new() -> Arc`, `snapshot()` - Status: COMPLETE ## T3: Filtered WAL Replay **File**: `tidal/src/cohort/rematerialization/replay.rs` - `ExclusionKey { entity_id, signal_type_id, weight_bits, timestamp_nanos }` 4-tuple - `filtered_replay(wal_dir, schema, community_id, contributions, signal_type_filter) -> Result` - Scans WAL segments; builds scratch `CohortSignalLedger`; excludes matched events - `ReplayStats { events_replayed, events_excluded }` - Status: COMPLETE ## T4: Atomic Ledger Swap **File**: `tidal/src/cohort/rematerialization/swap.rs` - `atomic_swap(live, scratch, community_id) -> u64` - Uses `drain_community_into` (no Clone needed); removes zero-event entries from live - Status: COMPLETE ## T5: Audit Log **File**: `tidal/src/cohort/rematerialization/audit.rs` - `AuditLogWriter::open(path)`, `append(&entry) -> Result<()>` - BLAKE3-framed binary: `[magic:4][body_len:4 LE][body:JSON][checksum:4]` - `PurgeAuditEntry`, `make_success_entry`, `make_failure_entry`, `read_audit_log` - `compute_verification_checksum_direct` — BLAKE3 over sorted live ledger entries - Status: COMPLETE ## T6: CohortSignalLedger Extensions **File**: `tidal/src/cohort/ledger.rs` - `remove_entry(cohort, entity_id, type_id)` — removes one DashMap entry - `drain_community_into(live, community_id) -> Vec<(EntityId, SignalTypeId)>` — ownership transfer via `DashMap::remove + insert` - Status: COMPLETE ## T7: RematerializationEngine Worker **File**: `tidal/src/cohort/rematerialization/mod.rs` - `RematerializationConfig` struct - `RematerializationHandle` with `shutdown()` and `Drop` - `start(config) -> RematerializationHandle` — spawns `tidal-remat` thread - `worker_loop`: polls queue every 500ms, executes jobs, exponential backoff on failure - Status: COMPLETE ## T8: TidalDb Integration **Files**: `tidal/src/db/mod.rs`, `tidal/src/db/rematerialization.rs`, `tidal/src/db/lifecycle.rs` - Added `purge_job_queue`, `rematerialization_metrics`, `rematerialization_handle` fields - Engine started in `from_parts()` when `config.data_dir` is Some - Engine stopped in `shutdown_inner()` before WAL shutdown - Interrupted jobs requeued on startup - Public methods: `submit_purge_job`, `purge_job_status`, `rematerialization_metrics` - Status: COMPLETE ## T9: Integration Tests **File**: `tidal/tests/m9_purge_remat.rs` - 6 tests: `submit_purge_job_returns_job_id`, `purge_job_status_pending_after_submit`, `unknown_job_id_returns_none`, `rematerialization_metrics_snapshot_is_valid`, `job_succeeds_in_persistent_mode`, `multiple_jobs_processed_independently` - Status: COMPLETE ## Bugfixes Applied - Fixed `purge_retracts_from_ledger` test in `cohort/purge.rs` (ancient timestamp caused full signal decay) - Fixed `query/retrieve/types/tests.rs` direct `Retrieve` struct initializers missing `community` field - Fixed `entities/community.rs` temporary drop while borrowed (DashMap entry) - Fixed `ranking/executor/mod.rs` `if let Some` on `Result` (bootstrapped code used wrong arm) - Replaced `uuid::Uuid` in `entities/revocation.rs` with `[u8; 16]` backed by timestamp+AtomicU64