/**
 * NERF Validators — Security output validation for quality gates.
 *
 * Ports NERF's six mode-specific validators as lightweight TypeScript
 * implementations. Each validator checks agent output against expected
 * format standards for its security mode.
 *
 * Validators:
 * - SigmaValidator (BLUE mode): Sigma rule YAML structure
 * - ForensicValidator (INCIDENT mode): JSON forensic report structure
 * - OSINTValidator (RECON mode): OSINT report structure
 * - DPIAValidator (PRIVACY mode): DPIA/privacy assessment structure
 * - PurpleValidator (PURPLE mode): ATT&CK coverage mapping structure
 * - ThreatModelValidator (ARCHITECT mode): Threat model structure
 */
import { existsSync } from "node:fs";
import { join } from "node:path";
import { parseAllDocuments } from "yaml";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function stripYamlCodeFences(text) {
    const matches = [...text.matchAll(/```(?:ya?ml)?\s*\n(.*?)```/gs)].map(m => m[1]);
    return matches.length > 0 ? matches.join("\n---\n") : text;
}
function stripJsonCodeFences(text) {
    const matches = [...text.matchAll(/```(?:json)?\s*\n(.*?)```/gs)].map(m => m[1]);
    return matches.length > 0 ? matches.join("\n") : text;
}
// ---------------------------------------------------------------------------
// SigmaValidator — validates Sigma detection rule YAML
// ---------------------------------------------------------------------------
const SIGMA_REQUIRED = ["title", "logsource", "detection"];
const SIGMA_RECOMMENDED = ["id", "status", "description", "level", "tags", "falsepositives"];
const SIGMA_VALID_LEVELS = ["critical", "high", "medium", "low", "informational"];
const SIGMA_ATTACK_RE = /attack\.t\d{4}(\.\d{3})?/;
function validateSigmaRule(rule) {
    const errors = [];
    const warnings = [];
    for (const f of SIGMA_REQUIRED) {
        if (!(f in rule))
            errors.push(`Missing required field: ${f}`);
    }
    for (const f of SIGMA_RECOMMENDED) {
        if (!(f in rule))
            warnings.push(`Missing recommended field: ${f}`);
    }
    if ("level" in rule && !SIGMA_VALID_LEVELS.includes(rule.level)) {
        errors.push(`Invalid level: ${rule.level}`);
    }
    const det = rule.detection;
    if (det && typeof det === "object" && !Array.isArray(det)) {
        if (!("condition" in det)) {
            errors.push("Detection missing 'condition' field");
        }
    }
    const tags = rule.tags;
    if (Array.isArray(tags) && tags.length > 0) {
        if (!tags.some(t => SIGMA_ATTACK_RE.test(String(t)))) {
            warnings.push("Tags contain no ATT&CK technique IDs");
        }
    }
    return { errors, warnings };
}
const sigmaValidator = {
    name: "SigmaValidator",
    mode: "BLUE",
    validate(text) {
        // Only trigger if text contains Sigma-like YAML
        if (!/\btitle\b.*\blogsource\b/s.test(text) && !/\bdetection\b/i.test(text))
            return null;
        try {
            const stripped = stripYamlCodeFences(text);
            // Lazy import yaml
            let documents;
            try {
                const docs = parseAllDocuments(stripped);
                documents = docs.map((d) => d.toJSON()).filter((d) => d != null);
            }
            catch (e) {
                return { valid: false, errors: [e.message], warnings: [], score: 0, metadata: { rules_count: 0 } };
            }
            if (documents.length === 0)
                return null;
            const allErrors = [];
            const allWarnings = [];
            let ruleCount = 0;
            for (let i = 0; i < documents.length; i++) {
                const doc = documents[i];
                if (typeof doc !== "object" || doc === null || Array.isArray(doc))
                    continue;
                ruleCount++;
                const { errors, warnings } = validateSigmaRule(doc);
                const prefix = documents.length > 1 ? `Rule ${i + 1}: ` : "";
                allErrors.push(...errors.map(e => prefix + e));
                allWarnings.push(...warnings.map(w => prefix + w));
            }
            if (ruleCount === 0)
                return null;
            return {
                valid: allErrors.length === 0,
                errors: allErrors,
                warnings: allWarnings,
                score: allErrors.length > 0 ? 0 : allWarnings.length > 0 ? 0.5 : 1.0,
                metadata: { rules_count: ruleCount },
            };
        }
        catch {
            return null;
        }
    },
};
// ---------------------------------------------------------------------------
// ForensicValidator — validates JSON forensic reports
// ---------------------------------------------------------------------------
const FORENSIC_REQUIRED = ["summary", "services", "connections", "findings", "recommendations"];
const FORENSIC_FINDING_FIELDS = ["severity", "description", "evidence"];
const CVE_VALID_RE = /CVE-\d{4}-\d{4,7}/;
const CVE_LIKE_RE = /CVE-[A-Za-z0-9]+-[A-Za-z0-9]+/g;
const forensicValidator = {
    name: "ForensicValidator",
    mode: "INCIDENT",
    validate(text) {
        if (!text.includes('"findings"') && !text.includes('"summary"'))
            return null;
        try {
            const stripped = stripJsonCodeFences(text);
            let parsed;
            try {
                parsed = JSON.parse(stripped);
            }
            catch {
                return null; // Not a JSON forensic report
            }
            if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
                return null;
            const report = parsed;
            // Check if this looks like a forensic report (has at least 2 required sections)
            const matchCount = FORENSIC_REQUIRED.filter(s => s in report).length;
            if (matchCount < 2)
                return null;
            const errors = [];
            const warnings = [];
            for (const s of FORENSIC_REQUIRED) {
                if (!(s in report))
                    errors.push(`Missing required section: ${s}`);
            }
            const findings = report.findings;
            if (Array.isArray(findings)) {
                if (findings.length === 0)
                    errors.push("findings list must not be empty");
                for (let i = 0; i < findings.length; i++) {
                    if (findings[i] && typeof findings[i] === "object") {
                        for (const f of FORENSIC_FINDING_FIELDS) {
                            if (!(f in findings[i])) {
                                warnings.push(`Finding ${i + 1}: missing '${f}'`);
                            }
                        }
                    }
                }
            }
            return {
                valid: errors.length === 0,
                errors,
                warnings,
                score: errors.length > 0 ? 0 : warnings.length > 0 ? 0.5 : 1.0,
                metadata: { findings_count: Array.isArray(findings) ? findings.length : 0 },
            };
        }
        catch {
            return null;
        }
    },
};
// ---------------------------------------------------------------------------
// OSINTValidator — validates OSINT report structure
// ---------------------------------------------------------------------------
const OSINT_REQUIRED = ["target", "findings"];
const OSINT_RECOMMENDED = ["methodology", "sources", "confidence_assessment"];
const osintValidator = {
    name: "OSINTValidator",
    mode: "RECON",
    validate(text) {
        if (!text.includes('"target"') || !text.includes('"findings"'))
            return null;
        try {
            const stripped = stripJsonCodeFences(text);
            let parsed;
            try {
                parsed = JSON.parse(stripped);
            }
            catch {
                return null;
            }
            if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
                return null;
            const report = parsed;
            const matchCount = OSINT_REQUIRED.filter(s => s in report).length;
            if (matchCount < 2)
                return null;
            const errors = [];
            const warnings = [];
            for (const s of OSINT_REQUIRED) {
                if (!(s in report))
                    errors.push(`Missing required field: ${s}`);
            }
            for (const s of OSINT_RECOMMENDED) {
                if (!(s in report))
                    warnings.push(`Missing recommended field: ${s}`);
            }
            const findings = report.findings;
            if (Array.isArray(findings) && findings.length === 0) {
                errors.push("findings list must not be empty");
            }
            return {
                valid: errors.length === 0,
                errors,
                warnings,
                score: errors.length > 0 ? 0 : warnings.length > 0 ? 0.5 : 1.0,
                metadata: { findings_count: Array.isArray(findings) ? findings.length : 0 },
            };
        }
        catch {
            return null;
        }
    },
};
// ---------------------------------------------------------------------------
// DPIAValidator — validates DPIA/privacy assessment structure
// ---------------------------------------------------------------------------
const DPIA_REQUIRED = ["processing_description", "necessity_assessment", "risks"];
const DPIA_RECOMMENDED = ["data_subjects", "legal_basis", "safeguards", "consultation"];
const dpiaValidator = {
    name: "DPIAValidator",
    mode: "PRIVACY",
    validate(text) {
        if (!text.includes('"processing_description"') && !text.includes('"risks"'))
            return null;
        try {
            const stripped = stripJsonCodeFences(text);
            let parsed;
            try {
                parsed = JSON.parse(stripped);
            }
            catch {
                return null;
            }
            if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
                return null;
            const report = parsed;
            const matchCount = DPIA_REQUIRED.filter(s => s in report).length;
            if (matchCount < 2)
                return null;
            const errors = [];
            const warnings = [];
            for (const s of DPIA_REQUIRED) {
                if (!(s in report))
                    errors.push(`Missing required field: ${s}`);
            }
            for (const s of DPIA_RECOMMENDED) {
                if (!(s in report))
                    warnings.push(`Missing recommended field: ${s}`);
            }
            const risks = report.risks;
            if (Array.isArray(risks)) {
                if (risks.length === 0)
                    errors.push("risks list must not be empty");
                for (let i = 0; i < risks.length; i++) {
                    const risk = risks[i];
                    if (risk && typeof risk === "object") {
                        if (!("likelihood" in risk))
                            warnings.push(`Risk ${i + 1}: missing 'likelihood'`);
                        if (!("severity" in risk))
                            warnings.push(`Risk ${i + 1}: missing 'severity'`);
                        if (!("mitigation" in risk))
                            warnings.push(`Risk ${i + 1}: missing 'mitigation'`);
                    }
                }
            }
            return {
                valid: errors.length === 0,
                errors,
                warnings,
                score: errors.length > 0 ? 0 : warnings.length > 0 ? 0.5 : 1.0,
                metadata: { risks_count: Array.isArray(risks) ? risks.length : 0 },
            };
        }
        catch {
            return null;
        }
    },
};
// ---------------------------------------------------------------------------
// PurpleValidator — validates ATT&CK coverage mapping
// ---------------------------------------------------------------------------
const PURPLE_REQUIRED = ["techniques", "coverage_summary"];
const ATTACK_TECHNIQUE_RE = /T\d{4}(\.\d{3})?/;
const purpleValidator = {
    name: "PurpleValidator",
    mode: "PURPLE",
    validate(text) {
        if (!text.includes('"techniques"') && !text.includes('"coverage_summary"'))
            return null;
        try {
            const stripped = stripJsonCodeFences(text);
            let parsed;
            try {
                parsed = JSON.parse(stripped);
            }
            catch {
                return null;
            }
            if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
                return null;
            const report = parsed;
            const matchCount = PURPLE_REQUIRED.filter(s => s in report).length;
            if (matchCount < 1)
                return null;
            const errors = [];
            const warnings = [];
            for (const s of PURPLE_REQUIRED) {
                if (!(s in report))
                    errors.push(`Missing required field: ${s}`);
            }
            const techniques = report.techniques;
            if (Array.isArray(techniques)) {
                if (techniques.length === 0)
                    errors.push("techniques list must not be empty");
                for (let i = 0; i < techniques.length; i++) {
                    const tech = techniques[i];
                    if (tech && typeof tech === "object") {
                        const tid = tech.technique_id || tech.id || "";
                        if (typeof tid === "string" && !ATTACK_TECHNIQUE_RE.test(tid)) {
                            warnings.push(`Technique ${i + 1}: invalid ATT&CK ID format '${tid}'`);
                        }
                        if (!("detection_status" in tech) && !("status" in tech)) {
                            warnings.push(`Technique ${i + 1}: missing detection status`);
                        }
                    }
                }
            }
            return {
                valid: errors.length === 0,
                errors,
                warnings,
                score: errors.length > 0 ? 0 : warnings.length > 0 ? 0.5 : 1.0,
                metadata: { techniques_count: Array.isArray(techniques) ? techniques.length : 0 },
            };
        }
        catch {
            return null;
        }
    },
};
// ---------------------------------------------------------------------------
// ThreatModelValidator — validates threat model structure
// ---------------------------------------------------------------------------
const THREAT_MODEL_REQUIRED = ["system_description", "threats"];
const THREAT_MODEL_RECOMMENDED = ["assumptions", "mitigations", "data_flow_diagram", "trust_boundaries"];
const threatModelValidator = {
    name: "ThreatModelValidator",
    mode: "ARCHITECT",
    validate(text) {
        if (!text.includes('"threats"') && !text.includes('"system_description"'))
            return null;
        try {
            const stripped = stripJsonCodeFences(text);
            let parsed;
            try {
                parsed = JSON.parse(stripped);
            }
            catch {
                return null;
            }
            if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
                return null;
            const report = parsed;
            const matchCount = THREAT_MODEL_REQUIRED.filter(s => s in report).length;
            if (matchCount < 1)
                return null;
            const errors = [];
            const warnings = [];
            for (const s of THREAT_MODEL_REQUIRED) {
                if (!(s in report))
                    errors.push(`Missing required field: ${s}`);
            }
            for (const s of THREAT_MODEL_RECOMMENDED) {
                if (!(s in report))
                    warnings.push(`Missing recommended field: ${s}`);
            }
            const threats = report.threats;
            if (Array.isArray(threats)) {
                if (threats.length === 0)
                    errors.push("threats list must not be empty");
                for (let i = 0; i < threats.length; i++) {
                    const threat = threats[i];
                    if (threat && typeof threat === "object") {
                        if (!("category" in threat) && !("stride_category" in threat)) {
                            warnings.push(`Threat ${i + 1}: missing STRIDE category`);
                        }
                        if (!("severity" in threat) && !("risk_rating" in threat)) {
                            warnings.push(`Threat ${i + 1}: missing severity/risk rating`);
                        }
                    }
                }
            }
            return {
                valid: errors.length === 0,
                errors,
                warnings,
                score: errors.length > 0 ? 0 : warnings.length > 0 ? 0.5 : 1.0,
                metadata: { threats_count: Array.isArray(threats) ? threats.length : 0 },
            };
        }
        catch {
            return null;
        }
    },
};
// ---------------------------------------------------------------------------
// Registry of all validators
// ---------------------------------------------------------------------------
const ALL_VALIDATORS = [
    sigmaValidator,
    forensicValidator,
    osintValidator,
    dpiaValidator,
    purpleValidator,
    threatModelValidator,
];
// ---------------------------------------------------------------------------
// Hook registration
// ---------------------------------------------------------------------------
function isNerfProject() {
    const cwd = process.cwd();
    return (existsSync(join(cwd, "config.yaml")) ||
        existsSync(join(cwd, ".nerf")) ||
        existsSync(join(cwd, "cli", "lib", "gateway")));
}
/**
 * Register NERF security validators as tool_result hooks.
 * When a tool result contains structured security output (Sigma rules,
 * forensic reports, etc.), the matching validator runs and logs findings.
 */
export function registerNerfValidators(pi) {
    pi.on("tool_result", async (event) => {
        if (!isNerfProject())
            return undefined;
        // Only validate text content from assistant tool results
        const content = event.content;
        if (typeof content !== "string" || content.length < 50)
            return undefined;
        // Run all validators, report findings from the first match
        for (const validator of ALL_VALIDATORS) {
            const result = validator.validate(content);
            if (result) {
                if (!result.valid) {
                    process.stderr.write(`[nerf:validator] ${validator.name} FAIL: ${result.errors.slice(0, 3).join("; ")}\n`);
                }
                else if (result.warnings.length > 0) {
                    process.stderr.write(`[nerf:validator] ${validator.name} WARN: ${result.warnings.slice(0, 3).join("; ")}\n`);
                }
                // Only report the first matching validator
                break;
            }
        }
        return undefined;
    });
}
// Export validators for testing
export { sigmaValidator as SigmaValidator, forensicValidator as ForensicValidator, osintValidator as OSINTValidator, dpiaValidator as DPIAValidator, purpleValidator as PurpleValidator, threatModelValidator as ThreatModelValidator, };
