/**
 * GapReportGenerator -- Identifies governance gaps in discovered agents.
 *
 * Maps missing controls to compliance framework requirements (SOC 2, HIPAA,
 * GDPR, PCI DSS, DORA, EU AI Act) and scores overall governance coverage.
 *
 * GATE 9: T-147
 *
 * @connexum/ai-governance
 */
import { createLogger } from '../util/log.js';
import { scanAgent, } from '../governance/owasp-agentic-scanner.js';
const log = createLogger('gap-report');
/**
 * Core governance controls that every agent should have.
 * Each control maps to one or more compliance frameworks.
 */
const GOVERNANCE_CONTROLS = [
    {
        controlId: 'GOV-001',
        name: 'Permissions File',
        description: 'Agent has a dedicated permissions file defining allowed/blocked tools',
        severity: 'CRITICAL',
        frameworks: ['soc2', 'hipaa', 'gdpr', 'pcidss', 'dora', 'euaiact'],
        check: (agent) => agent.governanceStatus === 'governed',
    },
    {
        controlId: 'GOV-002',
        name: 'Deny-by-Default Access',
        description: 'Agent tools are blocked unless explicitly allowed',
        severity: 'CRITICAL',
        frameworks: ['soc2', 'hipaa', 'pcidss'],
        check: (agent) => agent.governanceStatus !== 'ungoverned',
    },
    {
        controlId: 'GOV-003',
        name: 'Audit Trail',
        description: 'Agent actions are logged to an audit trail',
        severity: 'HIGH',
        frameworks: ['soc2', 'hipaa', 'gdpr', 'pcidss', 'dora', 'euaiact'],
        check: (agent) => agent.governanceStatus === 'governed',
    },
    {
        controlId: 'GOV-004',
        name: 'Tamper Detection',
        description: 'Governance files are protected by SHA-256 integrity checks',
        severity: 'HIGH',
        frameworks: ['soc2', 'hipaa', 'pcidss', 'dora'],
        check: (agent) => agent.governanceStatus === 'governed',
    },
    {
        controlId: 'GOV-005',
        name: 'Capability Boundaries',
        description: 'Agent has explicitly defined capability boundaries',
        severity: 'HIGH',
        frameworks: ['soc2', 'hipaa', 'euaiact'],
        check: (agent) => agent.capabilities.length > 0 && agent.governanceStatus !== 'ungoverned',
    },
    {
        controlId: 'GOV-006',
        name: 'Web Access Controls',
        description: 'Agents with web access have rate limiting and content safety rules',
        severity: 'MEDIUM',
        frameworks: ['soc2', 'gdpr', 'euaiact'],
        check: (agent) => {
            const hasWebCaps = agent.capabilities.some(c => c === 'web-search' || c === 'web-fetch' || c === 'browser');
            // If agent has web capabilities, it needs governance
            if (!hasWebCaps)
                return true;
            return agent.governanceStatus === 'governed';
        },
    },
    {
        controlId: 'GOV-007',
        name: 'Data Classification',
        description: 'Agent interactions are classified for sensitive data (PHI, PII)',
        severity: 'HIGH',
        frameworks: ['hipaa', 'gdpr', 'pcidss'],
        check: (agent) => agent.governanceStatus === 'governed',
    },
    {
        controlId: 'GOV-008',
        name: 'Incident Response',
        description: 'Agent has incident response procedures configured',
        severity: 'MEDIUM',
        frameworks: ['soc2', 'hipaa', 'gdpr', 'dora'],
        check: (agent) => agent.governanceStatus === 'governed',
    },
    {
        controlId: 'GOV-009',
        name: 'Transparency Labeling',
        description: 'AI-generated content is properly labeled',
        severity: 'MEDIUM',
        frameworks: ['euaiact', 'gdpr'],
        check: (agent) => agent.governanceStatus !== 'ungoverned',
    },
    {
        controlId: 'GOV-010',
        name: 'Human Oversight',
        description: 'Critical actions require human approval',
        severity: 'HIGH',
        frameworks: ['euaiact', 'dora', 'hipaa'],
        check: (agent) => agent.governanceStatus === 'governed',
    },
];
/**
 * Framework metadata for gap analysis.
 */
const FRAMEWORK_META = {
    soc2: {
        name: 'SOC 2 Type II',
        controlIds: ['GOV-001', 'GOV-002', 'GOV-003', 'GOV-004', 'GOV-005', 'GOV-006', 'GOV-008'],
    },
    hipaa: {
        name: 'HIPAA Security Rule',
        controlIds: ['GOV-001', 'GOV-002', 'GOV-003', 'GOV-004', 'GOV-005', 'GOV-007', 'GOV-008', 'GOV-010'],
    },
    gdpr: {
        name: 'GDPR',
        controlIds: ['GOV-001', 'GOV-003', 'GOV-006', 'GOV-007', 'GOV-008', 'GOV-009'],
    },
    pcidss: {
        name: 'PCI DSS v4.0',
        controlIds: ['GOV-001', 'GOV-002', 'GOV-003', 'GOV-004', 'GOV-007'],
    },
    dora: {
        name: 'DORA',
        controlIds: ['GOV-001', 'GOV-003', 'GOV-004', 'GOV-008', 'GOV-010'],
    },
    euaiact: {
        name: 'EU AI Act',
        controlIds: ['GOV-001', 'GOV-003', 'GOV-005', 'GOV-006', 'GOV-009', 'GOV-010'],
    },
};
export class GapReportGenerator {
    /**
     * Generate a gap report from scan results.
     *
     * OWASP-B2: each discovered agent is passed through the OWASP Agentic Top 10
     * scanner (scanAgent()) to produce OWASP-specific MissingControl entries.
     * These are merged with the existing GOV-xxx control findings so the CLI and
     * dashboard see a unified list of gaps per agent.
     *
     * @param scanResult - Output from AgentScanner.scan()
     * @param frameworks - Optional list of framework IDs to assess against (default: all)
     */
    generate(scanResult, frameworks) {
        const targetFrameworks = frameworks && frameworks.length > 0
            ? frameworks.filter(f => f in FRAMEWORK_META)
            : Object.keys(FRAMEWORK_META);
        log.info(`Generating gap report for ${scanResult.agents.length} agents against ${targetFrameworks.length} frameworks`);
        // Analyze each agent for gaps
        const agentGaps = scanResult.agents.map(agent => this.analyzeAgentGaps(agent, scanResult, targetFrameworks));
        // Build framework-level gap analysis
        const frameworkGaps = targetFrameworks.map(fwId => this.analyzeFrameworkGaps(fwId, agentGaps));
        // Compute overall score
        const overallScore = this.computeOverallScore(agentGaps);
        // Generate recommendations
        const recommendations = this.generateRecommendations(agentGaps, frameworkGaps);
        log.info(`Gap report complete. Overall score: ${overallScore}/100`);
        return {
            projectDir: scanResult.projectDir,
            generatedAt: new Date().toISOString(),
            agentGaps,
            frameworkGaps,
            overallScore,
            recommendations,
        };
    }
    /**
     * Analyze a single agent for missing governance controls.
     */
    analyzeAgentGaps(agent, scanResult, frameworks) {
        const relevantControls = GOVERNANCE_CONTROLS.filter(control => control.frameworks.some(f => frameworks.includes(f)));
        const missingControls = [];
        for (const control of relevantControls) {
            const hasCont = control.check(agent, scanResult);
            if (!hasCont) {
                missingControls.push({
                    controlId: control.controlId,
                    name: control.name,
                    description: control.description,
                    severity: control.severity,
                    frameworks: control.frameworks.filter(f => frameworks.includes(f)),
                });
            }
        }
        // OWASP-B2: run the OWASP Agentic Top 10 scanner against this agent
        // and merge the findings into missingControls as OWASP-specific controls.
        // The OWASP scanner provides structured findings that map directly to the
        // MissingControl schema (OWASP-B1 extended fix.remediationByLicenseState).
        const owaspManifest = this.buildAgentManifest(agent);
        const owaspReport = scanAgent(owaspManifest);
        const owaspControls = owaspReport.findings.map(f => this.owaspFindingToMissingControl(f));
        missingControls.push(...owaspControls);
        log.debug(`OWASP scan for ${agent.name}: ${owaspReport.findings.length} findings (${owaspReport.verdict})`);
        // Recalculate coverage score including OWASP controls
        const totalControls = relevantControls.length + owaspReport.findings.length;
        const metControls = relevantControls.length - (missingControls.length - owaspControls.length);
        const coverageScore = totalControls > 0
            ? Math.round((metControls / totalControls) * 100)
            : 100;
        return {
            name: agent.name,
            platform: agent.platform,
            filePath: agent.filePath,
            governanceStatus: agent.governanceStatus,
            missingControls,
            coverageScore,
        };
    }
    /**
     * Build an AgentManifest from a DiscoveredAgent so the OWASP scanner
     * can analyze it (OWASP-B2 integration).
     *
     * Maps from the scanner's generic capability list to the manifest fields
     * the OWASP analyzer reads:
     *   - tools: capability names (web-fetch → 'WebFetch', bash → 'Bash', etc.)
     *   - hooks: derived from governanceStatus ('governed' = all three hooks wired)
     *   - rateLimitsConfigured: true when governance is 'governed'
     *   - pluginIntegrityVerified: true when governance is 'governed'
     *   - declaredApprovalGates: empty when not governed (triggers A08 findings)
     */
    buildAgentManifest(agent) {
        // Map from discovered capability strings → tool names the OWASP scanner
        // uses in its classifiers (shell, network egress, filesystem write, db write).
        const CAP_TO_TOOL = {
            'bash': 'Bash',
            'web-search': 'WebSearch',
            'web-fetch': 'WebFetch',
            'browser': 'mcp__playwright',
            'file-read': 'Read',
            'file-write': 'Write',
            'git': 'Bash',
            'code-execution': 'Bash',
        };
        const tools = agent.capabilities
            .map(cap => CAP_TO_TOOL[cap])
            .filter((t) => t !== undefined);
        const isGoverned = agent.governanceStatus === 'governed';
        return {
            agentId: `discovered:${agent.name}`,
            name: agent.name,
            tools,
            hooks: isGoverned
                ? { preToolUse: true, postToolUse: true, postResponse: true }
                : { preToolUse: false, postToolUse: false, postResponse: false },
            rateLimitsConfigured: isGoverned,
            pluginIntegrityVerified: isGoverned,
            declaredApprovalGates: [],
            declaredForbiddenCapabilities: [],
        };
    }
    /**
     * Map an OWASP Agentic Top 10 finding to the MissingControl schema.
     *
     * OWASP control IDs use the format "OWASP-A01" ... "OWASP-A10".
     * The severity mapping: CRIT→CRITICAL, HIGH→HIGH, MED→MEDIUM, LOW→LOW.
     * The fix field carries the OWASP-B1 extended remediationByLicenseState.
     */
    owaspFindingToMissingControl(finding) {
        const SEV_MAP = {
            CRIT: 'CRITICAL',
            HIGH: 'HIGH',
            MED: 'MEDIUM',
            LOW: 'LOW',
        };
        // Derive a stable control ID from the OWASP risk ID
        // e.g. 'A01-prompt-injection' → 'OWASP-A01'
        const riskPrefix = finding.risk.match(/^(A\d+)/)?.[1] ?? 'AXX';
        const controlId = `OWASP-${riskPrefix}`;
        // Framework mapping: OWASP Top 10 for LLM agents is cross-framework
        const frameworks = ['soc2', 'hipaa', 'gdpr', 'euaiact'];
        const fix = {
            pack: 'owasp-company-pack',
            hook: 'PreToolUse:PromptInjectionDetector',
            sourceOfTruth: 'src/governance/owasp-agentic-scanner.ts',
            estimatedClosureMinutes: 30,
            remediationByLicenseState: {
                'not-purchased': {
                    summary: 'Purchase a Company Pack to enable OWASP Agentic Top 10 controls.',
                    command: 'npx @connexum/ai-governance onboard --pack company',
                },
                'purchased-not-active': {
                    summary: 'Activate your Company Pack on this agent to enforce OWASP controls.',
                    command: `npx @connexum/ai-governance bind --agent "${controlId}" --pack company`,
                },
                'active': {
                    summary: `Review ${finding.risk} configuration. ${finding.remediation}`,
                    command: `npx @connexum/ai-governance scan --agent . --fix ${controlId}`,
                },
            },
        };
        return {
            controlId,
            name: finding.title,
            description: finding.detail,
            severity: SEV_MAP[finding.severity],
            frameworks,
            fix,
        };
    }
    /**
     * Analyze gap coverage for a specific compliance framework.
     */
    analyzeFrameworkGaps(frameworkId, agentGaps) {
        const meta = FRAMEWORK_META[frameworkId];
        const unmetRequirements = [];
        for (const controlId of meta.controlIds) {
            const control = GOVERNANCE_CONTROLS.find(c => c.controlId === controlId);
            if (!control)
                continue;
            const affectedAgents = agentGaps
                .filter(ag => ag.missingControls.some(mc => mc.controlId === controlId))
                .map(ag => ag.name);
            if (affectedAgents.length > 0) {
                unmetRequirements.push({
                    requirementId: controlId,
                    description: control.description,
                    affectedAgents,
                });
            }
        }
        const totalControls = meta.controlIds.length;
        const metControls = totalControls - unmetRequirements.length;
        const coveragePercent = totalControls > 0
            ? Math.round((metControls / totalControls) * 100)
            : 100;
        return {
            frameworkId,
            frameworkName: meta.name,
            unmetRequirements,
            coveragePercent,
        };
    }
    /**
     * Compute overall governance coverage score.
     */
    computeOverallScore(agentGaps) {
        if (agentGaps.length === 0)
            return 100;
        const totalScore = agentGaps.reduce((sum, ag) => sum + ag.coverageScore, 0);
        return Math.round(totalScore / agentGaps.length);
    }
    /**
     * Generate prioritized recommendations based on gaps found.
     */
    generateRecommendations(agentGaps, frameworkGaps) {
        const recommendations = [];
        let priority = 0;
        // 1. Critical: Any ungoverned agents
        const ungoverned = agentGaps.filter(ag => ag.governanceStatus === 'ungoverned');
        if (ungoverned.length > 0) {
            priority++;
            recommendations.push({
                priority,
                title: 'Deploy governance to ungoverned agents',
                description: `${ungoverned.length} agent(s) have no governance controls. ` +
                    'Create PERMISSIONS.md files and enable deny-by-default access for each.',
                affectedAgents: ungoverned.map(a => a.name),
                impact: 'high',
            });
        }
        // 2. Critical: Missing permissions files
        const missingPerms = agentGaps.filter(ag => ag.missingControls.some(mc => mc.controlId === 'GOV-001'));
        if (missingPerms.length > 0 && missingPerms.length !== ungoverned.length) {
            priority++;
            recommendations.push({
                priority,
                title: 'Create dedicated permissions files',
                description: 'Create agent-specific PERMISSIONS.md files defining allowed and blocked tools.',
                affectedAgents: missingPerms.map(a => a.name),
                impact: 'high',
            });
        }
        // 3. High: Missing audit trails
        const missingAudit = agentGaps.filter(ag => ag.missingControls.some(mc => mc.controlId === 'GOV-003'));
        if (missingAudit.length > 0) {
            priority++;
            recommendations.push({
                priority,
                title: 'Enable audit logging',
                description: 'Configure audit trail logging for all agent actions. Required by SOC 2, HIPAA, GDPR, and PCI DSS.',
                affectedAgents: missingAudit.map(a => a.name),
                impact: 'high',
            });
        }
        // 4. High: Missing tamper detection
        const missingTamper = agentGaps.filter(ag => ag.missingControls.some(mc => mc.controlId === 'GOV-004'));
        if (missingTamper.length > 0) {
            priority++;
            recommendations.push({
                priority,
                title: 'Enable SHA-256 tamper detection',
                description: 'Enable governance file integrity verification using SHA-256 hashes.',
                affectedAgents: missingTamper.map(a => a.name),
                impact: 'medium',
            });
        }
        // 5. Medium: Missing web access controls
        const missingWebControls = agentGaps.filter(ag => ag.missingControls.some(mc => mc.controlId === 'GOV-006'));
        if (missingWebControls.length > 0) {
            priority++;
            recommendations.push({
                priority,
                title: 'Implement web access controls',
                description: 'Agents with web access capabilities need rate limiting, content safety rules, and isolation between fetch and action.',
                affectedAgents: missingWebControls.map(a => a.name),
                impact: 'medium',
            });
        }
        // 6. Medium: Framework-specific gaps
        for (const fw of frameworkGaps) {
            if (fw.coveragePercent < 50) {
                priority++;
                recommendations.push({
                    priority,
                    title: `Address ${fw.frameworkName} compliance gaps`,
                    description: `${fw.frameworkName} coverage is at ${fw.coveragePercent}%. ` +
                        `${fw.unmetRequirements.length} requirement(s) are unmet.`,
                    affectedAgents: [...new Set(fw.unmetRequirements.flatMap(r => r.affectedAgents))],
                    impact: 'medium',
                });
            }
        }
        // 7. Low: Partial agents that could become fully governed
        const partial = agentGaps.filter(ag => ag.governanceStatus === 'partial');
        if (partial.length > 0) {
            priority++;
            recommendations.push({
                priority,
                title: 'Upgrade partially governed agents to full governance',
                description: `${partial.length} agent(s) have some governance controls but are missing full coverage. ` +
                    'Review and complete their governance configuration.',
                affectedAgents: partial.map(a => a.name),
                impact: 'low',
            });
        }
        return recommendations;
    }
}
//# sourceMappingURL=gap-report.js.map