import { resolve, relative } from 'path';
import { realpathSync } from 'fs';
import { RiskClassifier } from './RiskClassifier.js';
import { PolicyEngine } from './PolicyEngine.js';
const SENTINEL_PATH_PREFIX = 'src/lib/autonomy/';
const SENTINEL_FILES = [
    'src/lib/autonomy/RiskClassifier.ts',
    'src/lib/autonomy/PolicyEngine.ts',
    'src/lib/autonomy/SecuritySentinel.ts',
    'src/lib/autonomy/EventBus.ts',
    'src/lib/autonomy/AuditStore.ts',
    'src/lib/autonomy/EventOutbox.ts',
    'src/lib/autonomy/schemas.ts',
    'src/lib/autonomy/index.ts',
];
const RECENT_DELETE_WINDOW_MS = 60_000;
export class SecuritySentinel {
    static instance = null;
    classifier;
    policyEngine;
    eventBus;
    auditStore;
    db;
    projectRoot;
    recentDeleteStmt;
    constructor(deps) {
        this.classifier = new RiskClassifier(deps.customRules);
        this.policyEngine = new PolicyEngine(deps.storage);
        this.eventBus = deps.eventBus;
        this.auditStore = deps.auditStore;
        this.db = deps.storage.getDatabase();
        this.projectRoot = deps.projectRoot ?? process.cwd();
        this.recentDeleteStmt = this.db.prepare(`SELECT COUNT(*) as count FROM audit_events
       WHERE agentId = ? AND actionType = 'file_delete' AND createdAt >= ?`);
    }
    static getInstance(deps) {
        if (!SecuritySentinel.instance) {
            SecuritySentinel.instance = new SecuritySentinel(deps);
        }
        return SecuritySentinel.instance;
    }
    static resetInstance() {
        SecuritySentinel.instance = null;
    }
    intercept(action) {
        const normalizedAction = this.normalizeAction(action);
        // Block paths outside project root
        if (normalizedAction.target.startsWith('..')) {
            const outsideRisk = {
                riskLevel: 'HIGH',
                score: 100,
                factors: ['Path resolves outside project root'],
                reasoning: 'Path traversal outside project root is blocked',
                blocked: true,
            };
            const decision = this.blockedDecision(outsideRisk);
            this.emitPolicyCheck(normalizedAction, outsideRisk, decision);
            const auditId = this.recordAudit(normalizedAction, outsideRisk, decision);
            return { allowed: false, riskLevel: 'HIGH', policyDecision: decision, auditId };
        }
        const context = this.buildContext(normalizedAction);
        const risk = this.classifier.classify(normalizedAction, context);
        const policyDecision = risk.blocked
            ? this.blockedDecision(risk)
            : this.policyEngine.evaluate(normalizedAction, risk);
        this.emitPolicyCheck(normalizedAction, risk, policyDecision);
        const auditId = this.recordAudit(normalizedAction, risk, policyDecision);
        return {
            allowed: policyDecision.allowed,
            riskLevel: risk.riskLevel,
            policyDecision,
            auditId,
        };
    }
    isImmutable() {
        return true;
    }
    getSentinelFiles() {
        return SENTINEL_FILES;
    }
    getClassifier() {
        return this.classifier;
    }
    getPolicyEngine() {
        return this.policyEngine;
    }
    normalizeAction(action) {
        const normalizedTarget = this.normalizePath(action.target);
        return { ...action, target: normalizedTarget };
    }
    normalizePath(target) {
        const resolved = resolve(this.projectRoot, target);
        const rel = relative(this.projectRoot, resolved).replace(/\\/g, '/');
        if (rel.startsWith('..'))
            return rel;
        try {
            const real = realpathSync(resolved);
            const realRel = relative(this.projectRoot, real).replace(/\\/g, '/');
            return realRel.startsWith('..') ? realRel : realRel;
        }
        catch {
            return rel;
        }
    }
    buildContext(action) {
        if (action.actionType !== 'file_delete')
            return undefined;
        const cutoff = new Date(Date.now() - RECENT_DELETE_WINDOW_MS).toISOString();
        const row = this.recentDeleteStmt.get(action.agentId, cutoff);
        return { recentDeleteCount: row.count };
    }
    blockedDecision(risk) {
        return {
            allowed: false,
            reason: `Sentinel files are protected: ${risk.reasoning}`,
            requiredAction: 'block',
            matchedPolicies: ['sentinel-immutability'],
        };
    }
    emitPolicyCheck(action, risk, decision) {
        try {
            this.eventBus.emit('policy_check', {
                actionEvent: {
                    agentId: action.agentId,
                    actionType: action.actionType,
                    target: action.target,
                    params: action.params,
                    riskLevel: risk.riskLevel,
                },
                policies: decision.matchedPolicies,
                result: decision.allowed ? 'allowed' : (decision.requiredAction === 'confirm' ? 'needs_confirmation' : 'blocked'),
                duration: 0,
            });
        }
        catch {
            // Policy check event emission failure should not break intercept
        }
    }
    recordAudit(action, risk, decision) {
        const outcome = decision.allowed ? 'allowed' : 'blocked';
        return this.auditStore.record({
            correlationId: action.correlationId ?? `sentinel-${Date.now()}`,
            eventType: 'policy_check',
            agentId: action.agentId,
            actionType: action.actionType,
            riskLevel: risk.riskLevel,
            payload: {
                target: action.target,
                riskScore: risk.score,
                factors: risk.factors,
                matchedPolicies: decision.matchedPolicies,
                reason: decision.reason,
            },
            outcome,
        });
    }
}
export function isSentinelProtectedPath(target) {
    const normalized = target.replace(/\\/g, '/').replace(/^\.\//, '');
    return normalized.startsWith(SENTINEL_PATH_PREFIX);
}
//# sourceMappingURL=SecuritySentinel.js.map