-- Phase 3: Compaction Schema -- T3.1-T3.4: Deduplication, GC, and dry-run support -- ============================================ -- STEP 1: Exact dedup tracking (T3.1) -- ============================================ CREATE TABLE IF NOT EXISTS exact_dedup_record ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(255) NOT NULL, -- Source and target edges source_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE, target_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE, -- Match criteria (all must match for exact dedup) source_match BOOLEAN NOT NULL, target_match BOOLEAN NOT NULL, relation_match BOOLEAN NOT NULL, fact_match BOOLEAN NOT NULL, -- Dedup decision dedup_action VARCHAR(20) DEFAULT 'pending' CHECK (dedup_action IN ('pending', 'merged', 'kept_separate', 'manual_review')), -- Metadata detected_at TIMESTAMPTZ DEFAULT NOW(), processed_at TIMESTAMPTZ, compaction_run_id UUID REFERENCES compaction_log(id) ON DELETE SET NULL, -- Dry-run support dry_run BOOLEAN DEFAULT FALSE, CONSTRAINT chk_unique_edge_pair UNIQUE (source_edge_id, target_edge_id, project_id) ); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_exact_dedup_project ON exact_dedup_record(project_id, dedup_action); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_exact_dedup_edges ON exact_dedup_record(source_edge_id, target_edge_id); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_exact_dedup_pending ON exact_dedup_record(project_id, detected_at) WHERE dedup_action = 'pending'; -- ============================================ -- STEP 2: Stale GC tracking (T3.1) -- ============================================ CREATE TABLE IF NOT EXISTS stale_gc_record ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(255) NOT NULL, -- Entity or edge marked for GC entity_id UUID REFERENCES memory_entity(id) ON DELETE CASCADE, edge_id UUID REFERENCES memory_edge(id) ON DELETE CASCADE, -- Staleness criteria age_days INT NOT NULL, t_invalid_at TIMESTAMPTZ, access_count BIGINT DEFAULT 0, -- GC decision gc_action VARCHAR(20) DEFAULT 'pending' CHECK (gc_action IN ('pending', 'deleted', 'archived', 'kept')), -- Metadata detected_at TIMESTAMPTZ DEFAULT NOW(), processed_at TIMESTAMPTZ, compaction_run_id UUID REFERENCES compaction_log(id) ON DELETE SET NULL, -- Dry-run support dry_run BOOLEAN DEFAULT FALSE, CONSTRAINT chk_entity_or_edge CHECK ( (entity_id IS NOT NULL AND edge_id IS NULL) OR (entity_id IS NULL AND edge_id IS NOT NULL) ) ); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stale_gc_project ON stale_gc_record(project_id, gc_action); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stale_gc_age ON stale_gc_record(project_id, age_days DESC) WHERE gc_action = 'pending'; CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stale_gc_invalid ON stale_gc_record(t_invalid_at) WHERE t_invalid_at IS NOT NULL AND gc_action = 'pending'; -- ============================================ -- STEP 3: Semantic dedup with LLM verification (T3.2) -- ============================================ CREATE TABLE IF NOT EXISTS semantic_dedup_record ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(255) NOT NULL, -- Source and target edges source_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE, target_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE, -- Pre-filter score (0-1, eliminates 60-70% of candidates) prefilter_score FLOAT NOT NULL, prefilter_passed BOOLEAN NOT NULL, -- LLM verification (if prefilter_passed = true) llm_model VARCHAR(100), llm_prompt TEXT, llm_response TEXT, llm_confidence FLOAT, llm_cost_usd FLOAT, -- Dedup decision dedup_action VARCHAR(50) DEFAULT 'pending' CHECK (dedup_action IN ( 'pending', 'auto_merged', 'auto_kept_separate', 'manual_review', 'llm_error', 'below_threshold' )), -- Merge strategy (if auto-merged) merge_strategy VARCHAR(50), -- 'keep_superset', 'keep_newer', 'keep_higher_confidence' merged_edge_id UUID REFERENCES memory_edge(id) ON DELETE SET NULL, -- Metadata detected_at TIMESTAMPTZ DEFAULT NOW(), processed_at TIMESTAMPTZ, compaction_run_id UUID REFERENCES compaction_log(id) ON DELETE SET NULL, -- Dry-run support dry_run BOOLEAN DEFAULT FALSE, CONSTRAINT chk_confidence_valid CHECK ( llm_confidence IS NULL OR (llm_confidence >= 0 AND llm_confidence <= 1) ), CONSTRAINT chk_prefilter_valid CHECK (prefilter_score >= 0 AND prefilter_score <= 1) ); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_semantic_dedup_project ON semantic_dedup_record(project_id, dedup_action); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_semantic_dedup_pending ON semantic_dedup_record(project_id, llm_confidence DESC NULLS LAST) WHERE dedup_action = 'manual_review' OR dedup_action = 'pending'; CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_semantic_dedup_edges ON semantic_dedup_record(source_edge_id, target_edge_id); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_semantic_dedup_merged ON semantic_dedup_record(project_id, merged_edge_id) WHERE merged_edge_id IS NOT NULL; -- ============================================ -- STEP 4: Compaction audit trail (T3.3) -- ============================================ CREATE TABLE IF NOT EXISTS compaction_audit ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(255) NOT NULL, compaction_run_id UUID NOT NULL REFERENCES compaction_log(id) ON DELETE CASCADE, -- Action details action_type VARCHAR(50) NOT NULL, -- 'exact_dedup', 'semantic_dedup', 'stale_gc', etc. source_id UUID, target_id UUID, -- Before state before_state JSONB NOT NULL, before_hash VARCHAR(64), -- After state after_state JSONB NOT NULL, after_hash VARCHAR(64), -- Provenance initiated_by VARCHAR(255), approval_status VARCHAR(50) DEFAULT 'pending' CHECK (approval_status IN ('pending', 'approved', 'rejected', 'auto')), approved_by VARCHAR(255), approval_reason TEXT, -- Rollback capability is_reversible BOOLEAN DEFAULT TRUE, reversal_instructions JSONB, -- Dry-run tracking dry_run BOOLEAN DEFAULT FALSE, -- Timestamp recorded_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_compaction_audit_run ON compaction_audit(compaction_run_id); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_compaction_audit_project ON compaction_audit(project_id, recorded_at DESC); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_compaction_audit_reversible ON compaction_audit(project_id, recorded_at DESC) WHERE is_reversible = TRUE; -- ============================================ -- STEP 5: Compaction dry-run validation -- ============================================ CREATE TABLE IF NOT EXISTS compaction_dryrun_result ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(255) NOT NULL, compaction_run_id UUID NOT NULL REFERENCES compaction_log(id) ON DELETE CASCADE, -- Dry-run metadata started_at TIMESTAMPTZ DEFAULT NOW(), completed_at TIMESTAMPTZ, -- Statistics exact_dedup_candidates INT DEFAULT 0, exact_dedup_safe INT DEFAULT 0, semantic_dedup_candidates INT DEFAULT 0, semantic_dedup_safe INT DEFAULT 0, semantic_dedup_manual_review INT DEFAULT 0, stale_gc_candidates INT DEFAULT 0, stale_gc_safe INT DEFAULT 0, -- Predicted impact predicted_space_freed_mb FLOAT DEFAULT 0.0, predicted_edge_count_reduction INT DEFAULT 0, predicted_entity_count_reduction INT DEFAULT 0, -- Validation issues found issues_found INT DEFAULT 0, issue_details JSONB DEFAULT '[]', -- Decision approval_recommended BOOLEAN DEFAULT FALSE, approval_reason TEXT ); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_dryrun_project ON compaction_dryrun_result(project_id, completed_at DESC); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_dryrun_run ON compaction_dryrun_result(compaction_run_id); -- ============================================ -- STEP 6: Scheduled compaction jobs (T3.4) -- ============================================ CREATE TABLE IF NOT EXISTS compaction_schedule ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(255) NOT NULL, -- Schedule config cron_expression VARCHAR(100) NOT NULL, -- e.g., "0 2 * * *" for daily at 2 AM UTC timezone VARCHAR(50) DEFAULT 'UTC', -- Execution config tier INT DEFAULT 1, -- 1 = exact dedup, 2 = semantic dedup, 3 = both dry_run_first BOOLEAN DEFAULT TRUE, auto_approve_safe_actions BOOLEAN DEFAULT FALSE, -- Resource limits max_execution_time_minutes INT DEFAULT 60, max_llm_cost_usd FLOAT DEFAULT 10.0, -- Status enabled BOOLEAN DEFAULT TRUE, -- Metadata created_at TIMESTAMPTZ DEFAULT NOW(), last_run_at TIMESTAMPTZ, next_run_at TIMESTAMPTZ, -- Notifications notify_on_completion BOOLEAN DEFAULT TRUE, notify_emails TEXT[] DEFAULT '{}' ); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_schedule_project ON compaction_schedule(project_id, enabled) WHERE enabled = TRUE; CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_schedule_next_run ON compaction_schedule(next_run_at) WHERE enabled = TRUE; -- ============================================ -- ROLLBACK INSTRUCTIONS -- ============================================ -- DROP TABLE IF EXISTS compaction_schedule; -- DROP TABLE IF EXISTS compaction_dryrun_result; -- DROP TABLE IF EXISTS compaction_audit; -- DROP TABLE IF EXISTS semantic_dedup_record; -- DROP TABLE IF EXISTS stale_gc_record; -- DROP TABLE IF EXISTS exact_dedup_record;