/**
 * src/autonomy/housekeeping.ts
 *
 * Daily 02:30 KSA cleanup of stale rows. Without this, tables that the
 * gateway writes append-only (cron_executions, kg_episodes, etc.) grow
 * unbounded over months/years and slow queries.
 *
 * Retention defaults (overridable via env):
 *   approval_requests      — pending+expired older than 7 days → DELETE
 *   escalation_queue       — pending older than 30 days → mark expired
 *                            resolved/dismissed older than 180 days → DELETE
 *   delivery_queue         — expired/failed older than 30 days → DELETE
 *   cron_executions        — all rows older than 90 days → DELETE
 *   subagent_runs          — completed/failed older than 180 days → DELETE
 *   kg_episodes            — older than 365 days AND entities_extracted=0 → DELETE
 *
 * The retention boundaries are conservative — kg_episodes gets a year and
 * even then only purges "empty" episodes (no entities extracted). Historical
 * audit trails matter more than disk savings for paid customers.
 */
import { supabase } from '../utils/supabase.js';
import { createChildLogger } from '../utils/logger.js';
const log = createChildLogger('autonomy:housekeeping');
const days = (n) => new Date(Date.now() - n * 24 * 60 * 60 * 1000).toISOString();
async function deleteWhere(table, build) {
    try {
        const q = supabase.from(table).delete();
        const final = build(q);
        const r = await (final.select ? final.select() : final);
        return (r.data ?? []).length;
    }
    catch (err) {
        log.debug({ table, err: err.message }, 'housekeeping delete failed');
        return 0;
    }
}
export async function runHousekeeping(tenantId) {
    const result = {
        approvals: 0, escalations: 0, deliveries: 0, cronExecutions: 0, subagents: 0, episodes: 0, totalDeleted: 0,
    };
    // approval_requests — pending old (7d) → delete
    try {
        const { data } = await supabase
            .from('approval_requests')
            .delete()
            .eq('tenant_id', tenantId)
            .eq('status', 'pending')
            .lt('expires_at', new Date().toISOString())
            .select('id');
        result.approvals = (data ?? []).length;
    }
    catch { /* table may not exist on legacy installs */ }
    // escalation_queue — pending older than 30d → mark expired
    try {
        await supabase
            .from('escalation_queue')
            .update({ status: 'expired' })
            .eq('tenant_id', tenantId)
            .eq('status', 'pending')
            .lt('created_at', days(30));
        // resolved/dismissed older than 180d → delete
        const { data } = await supabase
            .from('escalation_queue')
            .delete()
            .eq('tenant_id', tenantId)
            .in('status', ['resolved', 'dismissed', 'expired'])
            .lt('created_at', days(180))
            .select('id');
        result.escalations = (data ?? []).length;
    }
    catch { /* ignore */ }
    // delivery_queue — terminal old → delete
    try {
        const { data } = await supabase
            .from('delivery_queue')
            .delete()
            .eq('tenant_id', tenantId)
            .in('status', ['expired', 'failed', 'sent'])
            .lt('updated_at', days(30))
            .select('id');
        result.deliveries = (data ?? []).length;
    }
    catch { /* ignore */ }
    // cron_executions — keep last 90d
    try {
        const { data } = await supabase
            .from('cron_executions')
            .delete()
            .eq('tenant_id', tenantId)
            .lt('executed_at', days(90))
            .select('id');
        result.cronExecutions = (data ?? []).length;
    }
    catch { /* ignore */ }
    // subagent_runs — terminal older than 180d
    try {
        const { data } = await supabase
            .from('subagent_runs')
            .delete()
            .eq('tenant_id', tenantId)
            .in('status', ['completed', 'failed', 'cancelled'])
            .lt('created_at', days(180))
            .select('id');
        result.subagents = (data ?? []).length;
    }
    catch { /* ignore */ }
    // (Round-5) **Everlasting memory** — kg_episodes are NEVER purged.
    // Conversation history is the agent's permanent record; deleting it
    // would mean it forgets past customers, deals, lessons, context. The
    // user explicitly required this. Storage cost is acceptable (text
    // capped at 8K per row; SQLite handles millions of rows fine).
    // result.episodes stays at 0 here — the field is kept for back-compat.
    result.episodes = 0;
    // (v2.5.0) Profile extraction + vault rebuild — daily refresh of the
    // PROFILE.md self-learning system + Obsidian vault mirror. Both are
    // best-effort; failures don't block housekeeping.
    try {
        const { runProfileExtraction } = await import('./profile.js');
        const p = await runProfileExtraction(tenantId);
        log.info({ tenantId, ...p }, 'profile extraction during housekeeping');
    }
    catch (err) {
        log.debug({ err: err.message }, 'profile extraction failed during housekeeping');
    }
    try {
        const { rebuildVault } = await import('../knowledge/vault.js');
        const v = await rebuildVault(tenantId);
        log.info({ tenantId, ...v }, 'vault rebuild during housekeeping');
    }
    catch (err) {
        log.debug({ err: err.message }, 'vault rebuild failed during housekeeping');
    }
    result.totalDeleted = result.approvals + result.escalations + result.deliveries +
        result.cronExecutions + result.subagents + result.episodes;
    log.info({ tenantId, ...result }, 'housekeeping complete');
    return result;
}
//# sourceMappingURL=housekeeping.js.map