/**
 * src/autonomy/policies.ts
 *
 * Bounded-write autonomy. Owner sets caps per policy_key:
 *   - mode='auto'  → Muassis acts without asking.
 *   - mode='ask'   → Muassis prepares an escalation with its recommendation.
 *   - mode='never' → Hard stop. Refuses politely and explains why.
 *
 * Threshold check: if amount > threshold_amount, force 'ask' regardless of mode.
 *
 * Policy keys mirror the kinds of decisions an SMB employee makes:
 *   send_email, send_whatsapp, post_social, schedule_meeting,
 *   issue_invoice, pay_supplier, refund_customer, sign_contract,
 *   commit_purchase, hire_or_fire, change_pricing, public_statement, etc.
 */
import { randomUUID } from 'node:crypto';
import { supabase } from '../utils/supabase.js';
import { createChildLogger } from '../utils/logger.js';
const log = createChildLogger('autonomy:policies');
export const HARD_GATED_KEYS = new Set([
    'pay_supplier',
    'refund_customer',
    'sign_contract',
    'commit_purchase',
    'hire_or_fire',
    'change_pricing',
    'public_statement',
    'transfer_funds',
    'delete_data',
]);
export async function upsertPolicy(tenantId, input) {
    const existing = await getPolicy(tenantId, input.policy_key);
    const now = new Date().toISOString();
    if (existing) {
        const patched = {
            description: input.description ?? existing.description,
            mode: input.mode ?? existing.mode,
            threshold_amount: input.threshold_amount ?? existing.threshold_amount,
            threshold_currency: input.threshold_currency ?? existing.threshold_currency,
            conditions: input.conditions ?? existing.conditions,
            enabled: input.enabled ?? existing.enabled,
            updated_at: now,
        };
        const { error } = await supabase
            .from('autonomy_policies')
            .update(patched)
            .eq('tenant_id', tenantId)
            .eq('id', existing.id);
        if (error)
            throw new Error(`upsertPolicy update failed: ${error.message}`);
        return { ...existing, ...patched };
    }
    const row = {
        id: randomUUID(),
        tenant_id: tenantId,
        policy_key: input.policy_key,
        description: input.description ?? null,
        mode: input.mode ?? 'auto',
        threshold_amount: input.threshold_amount ?? null,
        threshold_currency: input.threshold_currency ?? 'SAR',
        conditions: input.conditions ?? {},
        enabled: input.enabled ?? true,
        created_at: now,
        updated_at: now,
    };
    const { error } = await supabase.from('autonomy_policies').insert(row);
    if (error)
        throw new Error(`upsertPolicy insert failed: ${error.message}`);
    log.info({ tenantId, policy_key: row.policy_key, mode: row.mode }, 'policy upserted');
    return row;
}
export async function getPolicy(tenantId, policyKey) {
    const { data, error } = await supabase
        .from('autonomy_policies')
        .select('*')
        .eq('tenant_id', tenantId)
        .eq('policy_key', policyKey)
        .maybeSingle();
    if (error) {
        log.warn({ error: error.message }, 'getPolicy failed');
        return null;
    }
    return data ?? null;
}
export async function listPolicies(tenantId) {
    const { data, error } = await supabase
        .from('autonomy_policies')
        .select('*')
        .eq('tenant_id', tenantId)
        .order('policy_key', { ascending: true });
    if (error) {
        log.warn({ error: error.message }, 'listPolicies failed');
        return [];
    }
    return (data ?? []);
}
/**
 * Decide whether Muassis can act, must ask, or must refuse.
 * Hard-gated keys always force at least 'ask' regardless of stored policy.
 */
export async function evaluate(input) {
    if (HARD_GATED_KEYS.has(input.policyKey)) {
        const policy = await getPolicy(input.tenantId, input.policyKey);
        if (policy?.mode === 'never') {
            return { allow: false, mode: 'never', reason: `${input.policyKey} is policy-blocked.`, policy };
        }
        return {
            allow: false,
            mode: 'ask',
            reason: `${input.policyKey} is hard-gated — owner approval required even when policy says auto.`,
            policy,
        };
    }
    const policy = await getPolicy(input.tenantId, input.policyKey);
    if (!policy || !policy.enabled) {
        // Default safety: unknown action → ask. SMB owner can flip a key to auto explicitly.
        return {
            allow: false,
            mode: 'ask',
            reason: `No active policy for ${input.policyKey}. Defaulting to ask-first.`,
            policy,
        };
    }
    if (policy.mode === 'never') {
        return { allow: false, mode: 'never', reason: `Policy says never act on ${input.policyKey}.`, policy };
    }
    if (policy.mode === 'ask') {
        return { allow: false, mode: 'ask', reason: `Policy says ask before acting on ${input.policyKey}.`, policy };
    }
    // mode === 'auto' — check threshold
    if (policy.threshold_amount != null && input.amount != null && input.amount > policy.threshold_amount) {
        return {
            allow: false,
            mode: 'ask',
            reason: `Amount ${input.amount} ${input.currency ?? policy.threshold_currency} exceeds auto-cap ${policy.threshold_amount} ${policy.threshold_currency}.`,
            policy,
        };
    }
    return { allow: true, mode: 'auto', reason: 'Within auto-policy bounds.', policy };
}
//# sourceMappingURL=policies.js.map