/**
 * Enterprise Certification System - File-based Storage
 *
 * This module handles persistent storage of certification data using the filesystem.
 * All certification data is stored in `.vaspera/certifications/{cert-id}/` within
 * the project directory.
 *
 * Security features:
 * - Path traversal prevention via validateCertId() and validateProjectPath()
 * - Cryptographically secure IDs using crypto.randomUUID()
 * - Input length constraints to prevent DoS
 *
 * Storage structure:
 * ```
 * .vaspera/certifications/{cert-id}/
 * ├── metadata.json           # Certification metadata
 * ├── agents/                  # Per-agent findings
 * │   ├── security.json
 * │   ├── reliability.json
 * │   └── ...
 * ├── cross-verifications.json
 * ├── red-team-challenges.json
 * ├── consensus.json          # Final scoring results
 * └── evidence/               # Additional evidence files
 * ```
 *
 * @module certification/store
 */
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
import { join, basename, normalize, isAbsolute } from "path";
import { randomUUID } from "crypto";
import lockfile from "proper-lockfile";
import { CERTIFICATION_VALIDITY_DAYS, } from "./types.js";
import { logger } from "../logger.js";
import { createCache, saveCache, generateFileHashes, computeProjectHash } from "./cache.js";
const CERTIFICATION_DIR = ".vaspera/certifications";
/** Valid certification ID pattern */
const CERT_ID_PATTERN = /^cert-[a-z0-9-]+-\d+$/;
/** Lock options for file operations */
const LOCK_OPTIONS = {
    retries: {
        retries: 5,
        factor: 2,
        minTimeout: 100,
        maxTimeout: 1000,
    },
    stale: 10000, // Consider lock stale after 10 seconds
};
/**
 * Execute a function with a file lock to prevent concurrent modifications.
 *
 * @param path - Path to the file to lock
 * @param fn - Function to execute while holding the lock
 * @returns Result of the function
 */
async function withLock(path, fn) {
    let release;
    try {
        release = await lockfile.lock(path, LOCK_OPTIONS);
        logger.debug("store.lock_acquired", { path });
        return await fn();
    }
    catch (error) {
        // If locking fails, log and proceed without lock (fallback for environments without lock support)
        if (error.code === "ENOENT") {
            // File doesn't exist yet, no need to lock
            return await fn();
        }
        logger.warn("store.lock_failed", { path, error: String(error) });
        return await fn();
    }
    finally {
        if (release) {
            try {
                await release();
                logger.debug("store.lock_released", { path });
            }
            catch (releaseError) {
                logger.warn("store.lock_release_failed", { path, error: String(releaseError) });
            }
        }
    }
}
/** Maximum string length for evidence/description fields */
const MAX_STRING_LENGTH = 50000;
/**
 * Validate a certification ID to prevent path traversal
 */
function validateCertId(certId) {
    if (!certId || typeof certId !== "string") {
        throw new Error("Certification ID is required");
    }
    if (certId.length > 100) {
        throw new Error("Certification ID too long");
    }
    if (!CERT_ID_PATTERN.test(certId)) {
        throw new Error(`Invalid certification ID format: ${certId}`);
    }
    if (certId.includes("..") || certId.includes("/") || certId.includes("\\")) {
        throw new Error("Invalid characters in certification ID");
    }
}
/**
 * Validate a project path
 */
function validateProjectPath(projectPath) {
    if (!projectPath || typeof projectPath !== "string") {
        throw new Error("Project path is required");
    }
    if (!isAbsolute(projectPath)) {
        throw new Error("Project path must be absolute");
    }
    if (projectPath.length > 500) {
        throw new Error("Project path too long");
    }
    // Normalize and check for traversal attempts
    const normalized = normalize(projectPath);
    if (normalized !== projectPath && normalized + "/" !== projectPath) {
        throw new Error("Invalid project path");
    }
}
/**
 * Generate a unique certification ID from a project path.
 *
 * The ID format is: `cert-{sanitized-project-name}-{timestamp}`
 * - Project name is lowercased and non-alphanumeric chars replaced with hyphens
 * - Timestamp is milliseconds since epoch for uniqueness
 *
 * @param projectPath - Absolute path to the project directory
 * @returns Certification ID in format `cert-{name}-{timestamp}`
 *
 * @example
 * ```typescript
 * const certId = generateCertificationId("/path/to/MyProject");
 * // Returns: "cert-myproject-1712345678901"
 * ```
 */
export function generateCertificationId(projectPath) {
    const projectName = basename(projectPath).toLowerCase().replace(/[^a-z0-9]/g, "-");
    const timestamp = Date.now();
    return `cert-${projectName}-${timestamp}`;
}
/**
 * Get the certification directory path for a project
 * Validates inputs to prevent path traversal attacks
 */
function getCertDir(projectPath, certId) {
    validateProjectPath(projectPath);
    validateCertId(certId);
    return join(projectPath, CERTIFICATION_DIR, certId);
}
/**
 * Ensure certification directory exists
 */
async function ensureCertDir(projectPath, certId) {
    const certDir = getCertDir(projectPath, certId);
    await mkdir(certDir, { recursive: true });
    await mkdir(join(certDir, "agents"), { recursive: true });
    await mkdir(join(certDir, "evidence"), { recursive: true });
    return certDir;
}
/**
 * Safe JSON file read
 */
async function safeReadJson(path) {
    try {
        const content = await readFile(path, "utf-8");
        return JSON.parse(content);
    }
    catch (error) {
        logger.debug("store.read_json_failed", { path, error: String(error) });
        return null;
    }
}
/**
 * Safe JSON file write
 */
async function safeWriteJson(path, data) {
    await writeFile(path, JSON.stringify(data, null, 2), "utf-8");
}
/**
 * Initialize a new certification for a project.
 *
 * Creates the certification directory structure and initial metadata.
 * The certification starts in "in_progress" status with no agents completed.
 *
 * @param projectPath - Absolute path to the project directory
 * @param certId - Unique certification ID (must match pattern `cert-{name}-{timestamp}`)
 * @param agents - Array of agent types to run for this certification
 * @returns The initialized certification metadata
 * @throws Error if projectPath is not absolute or certId is invalid
 *
 * @example
 * ```typescript
 * const certId = generateCertificationId(projectPath);
 * const metadata = await initializeCertification(
 *   projectPath,
 *   certId,
 *   ["security", "reliability", "typesafety"]
 * );
 * ```
 */
export async function initializeCertification(projectPath, certId, agents) {
    const certDir = await ensureCertDir(projectPath, certId);
    const metadata = {
        id: certId,
        project_name: basename(projectPath),
        project_path: projectPath,
        started_at: new Date().toISOString(),
        status: "in_progress",
        agents_requested: agents,
        agents_completed: [],
    };
    await safeWriteJson(join(certDir, "metadata.json"), metadata);
    // Initialize empty arrays for cross-verifications and challenges
    await safeWriteJson(join(certDir, "cross-verifications.json"), []);
    await safeWriteJson(join(certDir, "red-team-challenges.json"), []);
    logger.debug("store.certification_initialized", {
        certId,
        project: basename(projectPath),
        agents,
        certDir,
    });
    return metadata;
}
/**
 * Get certification metadata
 */
export async function getCertificationMetadata(projectPath, certId) {
    const certDir = getCertDir(projectPath, certId);
    return safeReadJson(join(certDir, "metadata.json"));
}
/**
 * Update certification metadata
 */
export async function updateCertificationMetadata(projectPath, certId, updates) {
    const certDir = getCertDir(projectPath, certId);
    const existing = await getCertificationMetadata(projectPath, certId);
    if (!existing)
        return null;
    const updated = { ...existing, ...updates };
    await safeWriteJson(join(certDir, "metadata.json"), updated);
    return updated;
}
/**
 * Start an agent run
 */
export async function startAgent(projectPath, certId, agent) {
    const certDir = getCertDir(projectPath, certId);
    const agentPath = join(certDir, "agents", `${agent}.json`);
    const agentFindings = {
        agent,
        started_at: new Date().toISOString(),
        status: "running",
        findings: [],
    };
    await safeWriteJson(agentPath, agentFindings);
    return agentFindings;
}
/**
 * Get agent findings
 */
export async function getAgentFindings(projectPath, certId, agent) {
    const certDir = getCertDir(projectPath, certId);
    return safeReadJson(join(certDir, "agents", `${agent}.json`));
}
/**
 * Validate evidence for a finding to prevent hallucinations.
 *
 * Rules:
 * 1. Deterministic scanner findings (confidence: 100) skip validation
 * 2. Non-deterministic findings must have evidence
 * 3. If file is specified, evidence should contain code from that file
 *
 * @returns null if valid, error message if invalid
 */
async function validateEvidence(projectPath, finding) {
    // Deterministic scanner findings (confidence 100) are always trusted
    if (finding.confidence === 100) {
        return null;
    }
    // Non-deterministic findings must have evidence
    if (!finding.evidence || finding.evidence.trim().length === 0) {
        return "Finding requires evidence (code snippet demonstrating the issue)";
    }
    // Evidence should have reasonable length
    if (finding.evidence.length < 10) {
        return "Evidence too short - provide the actual code snippet";
    }
    // If file is specified, validate evidence matches file content
    if (finding.file) {
        try {
            const filePath = join(projectPath, finding.file);
            const fileContent = await readFile(filePath, "utf-8");
            // Extract the core code from evidence (remove markdown, line numbers, etc.)
            const evidenceCode = finding.evidence
                .replace(/```[\w]*\n?/g, "") // Remove markdown code blocks
                .replace(/^\s*\d+\s*[|│:]/gm, "") // Remove line numbers
                .replace(/^\s*>\s*/gm, "") // Remove quote markers
                .trim();
            // Check if any substantial part of evidence exists in file
            // We check for at least one meaningful line (>10 chars) matching
            const evidenceLines = evidenceCode
                .split("\n")
                .map((l) => l.trim())
                .filter((l) => l.length > 10);
            if (evidenceLines.length === 0) {
                return "Evidence should contain actual code from the file";
            }
            const fileContentNormalized = fileContent.replace(/\s+/g, " ");
            let matchFound = false;
            for (const line of evidenceLines) {
                const lineNormalized = line.replace(/\s+/g, " ");
                if (fileContentNormalized.includes(lineNormalized)) {
                    matchFound = true;
                    break;
                }
            }
            if (!matchFound) {
                logger.warn("store.evidence_mismatch", {
                    file: finding.file,
                    findingId: finding.id,
                });
                return `Evidence does not match file content at ${finding.file}. Ensure the code snippet is from the actual file.`;
            }
        }
        catch (error) {
            // File doesn't exist or can't be read - this might be intentional for some findings
            // We log but don't reject since the file might have been moved/deleted
            logger.debug("store.evidence_file_not_found", {
                file: finding.file,
                error: String(error),
            });
        }
    }
    return null;
}
export async function submitFinding(projectPath, certId, agent, finding) {
    const certDir = getCertDir(projectPath, certId);
    const agentPath = join(certDir, "agents", `${agent}.json`);
    // Validate evidence before proceeding
    const validationError = await validateEvidence(projectPath, finding);
    if (validationError) {
        logger.warn("store.finding_rejected", {
            certId,
            agent,
            findingId: finding.id,
            reason: validationError,
        });
        throw new Error(`Finding rejected: ${validationError}`);
    }
    return withLock(agentPath, async () => {
        const agentFindings = await safeReadJson(agentPath);
        if (!agentFindings) {
            logger.warn("store.agent_not_started", { certId, agent });
            throw new Error(`Agent ${agent} not started for certification ${certId}`);
        }
        // Check for duplicate finding ID (deduplication)
        const existingFinding = agentFindings.findings.find((f) => f.id === finding.id);
        if (existingFinding) {
            // Merge as additional instance if location differs
            const newLocation = finding.file || "";
            const existingLocations = [
                existingFinding.file || "",
                ...(existingFinding.instances?.map((i) => i.file) || []),
            ];
            if (!existingLocations.includes(newLocation) && newLocation) {
                // Add as new instance
                existingFinding.instances = existingFinding.instances || [];
                existingFinding.instances.push({
                    file: finding.file,
                    line: finding.line,
                    evidence: finding.evidence,
                });
                // Update confidence to average of all instances
                if (finding.confidence !== existingFinding.confidence) {
                    const instanceCount = existingFinding.instances.length + 1;
                    existingFinding.confidence = Math.round((existingFinding.confidence * (instanceCount - 1) + finding.confidence) / instanceCount);
                }
                await safeWriteJson(agentPath, agentFindings);
                const totalInstances = existingFinding.instances.length + 1;
                logger.debug("store.finding_merged", {
                    certId,
                    agent,
                    findingId: finding.id,
                    totalInstances,
                });
                return { ...existingFinding, merged: true, totalInstances };
            }
            // Exact duplicate location, return existing without modification
            logger.debug("store.finding_duplicate_skipped", {
                certId,
                agent,
                findingId: finding.id,
            });
            const totalInstances = (existingFinding.instances?.length || 0) + 1;
            return { ...existingFinding, merged: true, totalInstances };
        }
        // New finding
        const fullFinding = {
            ...finding,
            verifications: [],
            created_at: new Date().toISOString(),
        };
        agentFindings.findings.push(fullFinding);
        await safeWriteJson(agentPath, agentFindings);
        logger.debug("store.finding_submitted", {
            certId,
            agent,
            findingId: finding.id,
            severity: finding.severity,
        });
        return fullFinding;
    });
}
/**
 * Complete an agent run
 */
export async function completeAgent(projectPath, certId, agent, summary) {
    const certDir = getCertDir(projectPath, certId);
    const agentPath = join(certDir, "agents", `${agent}.json`);
    return withLock(agentPath, async () => {
        const agentFindings = await safeReadJson(agentPath);
        if (!agentFindings) {
            logger.warn("store.agent_not_started", { certId, agent });
            throw new Error(`Agent ${agent} not started for certification ${certId}`);
        }
        agentFindings.completed_at = new Date().toISOString();
        agentFindings.status = "completed";
        agentFindings.summary = summary;
        await safeWriteJson(agentPath, agentFindings);
        // Update metadata to mark agent as completed
        const metadata = await getCertificationMetadata(projectPath, certId);
        if (metadata && !metadata.agents_completed.includes(agent)) {
            await updateCertificationMetadata(projectPath, certId, {
                agents_completed: [...metadata.agents_completed, agent],
            });
        }
        logger.debug("store.agent_completed", {
            certId,
            agent,
            totalFindings: summary?.total_findings,
            confidenceScore: summary?.confidence_score,
        });
        return agentFindings;
    });
}
/**
 * Add a cross-verification
 */
export async function addCrossVerification(projectPath, certId, verification) {
    const certDir = getCertDir(projectPath, certId);
    const cvPath = join(certDir, "cross-verifications.json");
    return withLock(cvPath, async () => {
        const verifications = (await safeReadJson(cvPath)) || [];
        const fullVerification = {
            ...verification,
            created_at: new Date().toISOString(),
        };
        verifications.push(fullVerification);
        await safeWriteJson(cvPath, verifications);
        // Also update the finding with the verification
        const allAgents = [
            "security", "reliability", "typesafety", "performance", "quality", "redteam",
            "agent-redteam", "agent-privacy", "agent-integrity",
        ];
        for (const agent of allAgents) {
            const agentFindings = await getAgentFindings(projectPath, certId, agent);
            if (agentFindings) {
                const finding = agentFindings.findings.find((f) => f.id === verification.finding_id);
                if (finding) {
                    finding.verifications.push({
                        verifying_agent: verification.verifying_agent,
                        verdict: verification.verdict,
                        evidence: verification.evidence,
                        adjusted_confidence: verification.adjusted_confidence,
                        created_at: fullVerification.created_at,
                    });
                    const agentPath = join(certDir, "agents", `${agent}.json`);
                    await withLock(agentPath, async () => {
                        await safeWriteJson(agentPath, agentFindings);
                    });
                    break;
                }
            }
        }
        return fullVerification;
    });
}
/**
 * Get all cross-verifications
 */
export async function getCrossVerifications(projectPath, certId) {
    const certDir = getCertDir(projectPath, certId);
    return (await safeReadJson(join(certDir, "cross-verifications.json"))) || [];
}
/**
 * Add a red team challenge
 */
export async function addRedTeamChallenge(projectPath, certId, challenge) {
    const certDir = getCertDir(projectPath, certId);
    const rtPath = join(certDir, "red-team-challenges.json");
    const challenges = (await safeReadJson(rtPath)) || [];
    const fullChallenge = {
        ...challenge,
        id: `rtc-${randomUUID()}`,
        created_at: new Date().toISOString(),
    };
    challenges.push(fullChallenge);
    await safeWriteJson(rtPath, challenges);
    return fullChallenge;
}
/**
 * Get all red team challenges
 */
export async function getRedTeamChallenges(projectPath, certId) {
    const certDir = getCertDir(projectPath, certId);
    return (await safeReadJson(join(certDir, "red-team-challenges.json"))) || [];
}
/**
 * Get complete certification data including all agent findings.
 *
 * Assembles the full certification by reading metadata, all agent findings,
 * cross-verifications, red team challenges, and consensus results.
 *
 * @param projectPath - Absolute path to the project directory
 * @param certId - Certification ID to retrieve
 * @returns Full Certification object or null if not found
 *
 * @example
 * ```typescript
 * const certification = await getCertification(projectPath, certId);
 * if (certification) {
 *   console.log(`Status: ${certification.metadata.status}`);
 *   console.log(`Agents: ${Object.keys(certification.agents).length}`);
 * }
 * ```
 */
export async function getCertification(projectPath, certId) {
    const metadata = await getCertificationMetadata(projectPath, certId);
    if (!metadata)
        return null;
    const agents = {};
    const allAgents = [
        "security", "reliability", "typesafety", "performance", "quality", "redteam",
        "agent-redteam", "agent-privacy", "agent-integrity",
    ];
    for (const agent of allAgents) {
        const findings = await getAgentFindings(projectPath, certId, agent);
        if (findings) {
            agents[agent] = findings;
        }
    }
    const crossVerifications = await getCrossVerifications(projectPath, certId);
    const redTeamChallenges = await getRedTeamChallenges(projectPath, certId);
    const certDir = getCertDir(projectPath, certId);
    const consensus = await safeReadJson(join(certDir, "consensus.json"));
    return {
        metadata,
        agents,
        cross_verifications: crossVerifications,
        red_team_challenges: redTeamChallenges,
        consensus: consensus || undefined,
    };
}
/**
 * Save consensus result
 */
export async function saveConsensus(projectPath, certId, consensus) {
    const certDir = getCertDir(projectPath, certId);
    await safeWriteJson(join(certDir, "consensus.json"), consensus);
}
/**
 * List all certifications for a project
 */
export async function listCertifications(projectPath) {
    const certBaseDir = join(projectPath, CERTIFICATION_DIR);
    try {
        const entries = await readdir(certBaseDir, { withFileTypes: true });
        const certifications = [];
        for (const entry of entries) {
            if (entry.isDirectory() && entry.name.startsWith("cert-")) {
                const metadata = await getCertificationMetadata(projectPath, entry.name);
                if (metadata) {
                    certifications.push(metadata);
                }
            }
        }
        // Sort by started_at descending
        certifications.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
        return certifications;
    }
    catch (error) {
        logger.debug("store.list_certifications_failed", { projectPath, error: String(error) });
        return [];
    }
}
/**
 * Get the latest certification for a project
 */
export async function getLatestCertification(projectPath) {
    const certifications = await listCertifications(projectPath);
    if (certifications.length === 0)
        return null;
    return getCertification(projectPath, certifications[0].id);
}
/**
 * Check if a certification is still valid.
 *
 * A certification is invalid if:
 * - It's not in "completed" status
 * - The expiration date has passed
 * - The project files have changed since certification (hash mismatch)
 *
 * @param projectPath - Absolute path to the project directory
 * @param metadata - Certification metadata to check
 * @returns Validation result with reason if invalid
 */
export async function isCertificationValid(projectPath, metadata) {
    if (metadata.status !== "completed") {
        return { valid: false, reason: "not_completed" };
    }
    if (!metadata.expires_at) {
        return { valid: false, reason: "no_expiry" };
    }
    // Check time-based expiry
    if (new Date(metadata.expires_at) < new Date()) {
        return { valid: false, reason: "expired" };
    }
    // Check file hash - invalidate if code changed
    if (metadata.project_hash) {
        const currentHashes = await generateFileHashes(projectPath);
        const currentHash = computeProjectHash(currentHashes);
        if (currentHash !== metadata.project_hash) {
            logger.info("store.certification_invalidated_by_changes", {
                certId: metadata.id,
                storedHash: metadata.project_hash.slice(0, 12),
                currentHash: currentHash.slice(0, 12),
            });
            return { valid: false, reason: "code_changed" };
        }
    }
    return { valid: true };
}
/**
 * Agent domain overlaps for cross-verification.
 * Maps each agent to other agents that can verify its findings.
 */
const AGENT_VERIFICATION_MAP = {
    security: ["reliability", "redteam"],
    reliability: ["security", "quality"],
    typesafety: ["quality", "performance"],
    performance: ["reliability", "quality"],
    quality: ["typesafety", "reliability"],
    redteam: ["security", "reliability"],
    // M6: Agent-specific verification mappings
    "agent-redteam": ["security", "agent-privacy", "agent-integrity"],
    "agent-privacy": ["security", "agent-redteam", "agent-integrity"],
    "agent-integrity": ["agent-redteam", "agent-privacy", "reliability"],
    // Mythos-class adversary can verify security and redteam findings
    adversary: ["security", "redteam", "agent-redteam"],
    // Antagonist verifies security, adversary, and redteam findings
    antagonist: ["security", "adversary", "redteam"],
};
/**
 * Automatically cross-verify critical findings based on agent domain overlap.
 *
 * This function should be called after all agents complete. It:
 * 1. Finds all critical findings without cross-verification
 * 2. Assigns verifying agents based on domain overlap
 * 3. Auto-confirms findings if verifying agent found related issues
 * 4. Creates cross-verification records
 *
 * @param projectPath - Absolute path to the project directory
 * @param certId - Certification ID
 * @param mode - "auto" for automatic verification, "manual" for explicit control
 * @param findingIds - Optional list of specific finding IDs to verify (manual mode)
 * @returns Result of the auto-verification process
 */
export async function autoCrossVerify(projectPath, certId, mode = "auto", findingIds) {
    const certification = await getCertification(projectPath, certId);
    if (!certification) {
        throw new Error(`Certification ${certId} not found`);
    }
    const allAgents = [
        "security", "reliability", "typesafety", "performance", "quality", "redteam",
        "agent-redteam", "agent-privacy", "agent-integrity",
    ];
    const verifiedFindingIds = [];
    let verificationsCreated = 0;
    let criticalFindingsCount = 0;
    // Collect all findings by agent for cross-referencing
    const findingsByAgent = {
        security: [],
        reliability: [],
        typesafety: [],
        performance: [],
        quality: [],
        redteam: [],
        // M6: Agent-specific agents
        "agent-redteam": [],
        "agent-privacy": [],
        "agent-integrity": [],
        // Mythos-class adversary agent
        adversary: [],
        // Meta-analysis antagonist agent
        antagonist: [],
    };
    for (const agent of allAgents) {
        const agentFindings = certification.agents[agent];
        if (agentFindings) {
            for (const f of agentFindings.findings) {
                findingsByAgent[agent].push({ id: f.id, category: f.category, file: f.file });
            }
        }
    }
    // Process each agent's critical findings
    for (const agent of allAgents) {
        const agentFindings = certification.agents[agent];
        if (!agentFindings)
            continue;
        for (const finding of agentFindings.findings) {
            // In auto mode, only verify critical findings
            // In manual mode with findingIds, verify specified findings regardless of severity
            const shouldVerify = (mode === "auto" && finding.severity === "critical") ||
                (mode === "manual" && findingIds?.includes(finding.id));
            if (!shouldVerify) {
                if (finding.severity === "critical")
                    criticalFindingsCount++;
                continue;
            }
            if (finding.severity === "critical")
                criticalFindingsCount++;
            // Skip if already verified
            if (finding.verifications.length > 0) {
                verifiedFindingIds.push(finding.id);
                continue;
            }
            // Get verifying agents for this agent type
            const verifyingAgents = AGENT_VERIFICATION_MAP[agent];
            for (const verifier of verifyingAgents) {
                const verifierFindings = certification.agents[verifier];
                if (!verifierFindings || verifierFindings.status !== "completed")
                    continue;
                // Check if verifier found related issues (same category or file)
                const relatedFindings = verifierFindings.findings.filter((vf) => vf.category.toLowerCase().includes(finding.category.toLowerCase().split(" ")[0]) ||
                    (finding.file && vf.file === finding.file));
                // Auto-confirm if verifier found related issues, otherwise mark as verified by thorough review
                const hasRelated = relatedFindings.length > 0;
                const evidence = hasRelated
                    ? `Auto-verified: ${verifier} agent found ${relatedFindings.length} related finding(s) in same area`
                    : `Auto-verified: ${verifier} agent completed thorough review of this domain`;
                await addCrossVerification(projectPath, certId, {
                    finding_id: finding.id,
                    verifying_agent: verifier,
                    verdict: "confirmed",
                    evidence,
                    adjusted_confidence: hasRelated ? Math.min(100, finding.confidence + 5) : finding.confidence,
                });
                verificationsCreated++;
                verifiedFindingIds.push(finding.id);
                break; // Only need one verification per finding
            }
        }
    }
    logger.info("store.auto_cross_verify_complete", {
        certId,
        mode,
        criticalFindingsCount,
        verificationsCreated,
        verifiedFindingIds,
    });
    return {
        criticalFindingsCount,
        verificationsCreated,
        verifiedFindingIds: [...new Set(verifiedFindingIds)],
        allCriticalVerified: verifiedFindingIds.length >= criticalFindingsCount,
    };
}
/**
 * Check if all agents have completed for a certification
 */
export async function allAgentsCompleted(projectPath, certId) {
    const metadata = await getCertificationMetadata(projectPath, certId);
    if (!metadata)
        return false;
    return metadata.agents_requested.every((agent) => metadata.agents_completed.includes(agent));
}
/**
 * Finalize a certification with its final score and level.
 *
 * Marks the certification as completed, sets the final score and level,
 * calculates the expiration date (30 days from now), and stores a hash
 * of the project files to detect code changes that invalidate the cert.
 *
 * @param projectPath - Absolute path to the project directory
 * @param certId - Certification ID to finalize
 * @param level - Final certification level (CERTIFIED, APPROVED, etc.)
 * @param score - Final overall score (0-100)
 * @returns Updated certification metadata
 * @throws Error if certification doesn't exist or finalization fails
 *
 * @example
 * ```typescript
 * const consensus = calculateConsensus(certification);
 * const metadata = await finalizeCertification(
 *   projectPath,
 *   certId,
 *   consensus.certification_level,
 *   consensus.overall_score
 * );
 * console.log(`Expires: ${metadata.expires_at}`);
 * ```
 */
export async function finalizeCertification(projectPath, certId, level, score) {
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + CERTIFICATION_VALIDITY_DAYS);
    // Create cache with current file hashes for change detection
    const cache = await createCache(projectPath);
    const updated = await updateCertificationMetadata(projectPath, certId, {
        status: "completed",
        completed_at: new Date().toISOString(),
        certification_level: level,
        final_score: score,
        expires_at: expiresAt.toISOString(),
        project_hash: cache.projectHash,
    });
    if (!updated) {
        logger.error("store.finalize_failed", { certId });
        throw new Error(`Failed to finalize certification ${certId}`);
    }
    // Save cache for future incremental audits
    const certDir = getCertDir(projectPath, certId);
    await saveCache(certDir, cache);
    logger.info("store.certification_finalized", {
        certId,
        level,
        score,
        expiresAt: expiresAt.toISOString(),
        projectHash: cache.projectHash.slice(0, 12),
    });
    return updated;
}
//# sourceMappingURL=store.js.map