"use strict";
/**
 * Confidence Adjustment Rules
 *
 * Data-driven list of all adjustment rules, organized by concern.
 * Each rule has a name, source, applies() predicate, delta, and reason.
 *
 * Rules are evaluated against every finding; all matching rules
 * contribute their delta to the final confidence score.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.FRAMEWORK_RULES = exports.ROUTE_RULES = exports.TAINT_RULES = exports.AUTO_DISMISS_RULES = exports.FILE_CONTEXT_RULES = exports.ALL_ADJUSTMENT_RULES = void 0;
const file_classifier_1 = require("../parse/file-classifier");
// ============================================================================
// File Context Rules (active now)
// ============================================================================
const FILE_CONTEXT_RULES = [
    {
        name: 'test_file',
        source: 'file_context',
        applies: (finding, ctx) => (0, file_classifier_1.isTestOrMockFile)(finding.filePath),
        delta: -0.35,
        reason: 'Finding in test/mock file',
    },
    {
        name: 'example_file',
        source: 'file_context',
        applies: (finding, ctx) => (0, file_classifier_1.isExampleFile)(finding.filePath),
        delta: -0.30,
        reason: 'Finding in example/demo file',
    },
    {
        name: 'doc_file',
        source: 'file_context',
        applies: (finding) => /\.(md|mdx|txt|rst)$/i.test(finding.filePath),
        delta: -0.30,
        reason: 'Finding in documentation file',
    },
    {
        name: 'scanner_code',
        source: 'file_context',
        applies: (finding) => (0, file_classifier_1.isScannerOrFixtureFile)(finding.filePath),
        delta: -0.30,
        reason: 'Finding in scanner/fixture code',
    },
    {
        name: 'tooling_dir',
        source: 'file_context',
        applies: (finding) => (0, file_classifier_1.isToolingDirectory)(finding.filePath),
        delta: -0.25,
        reason: 'Finding in tooling/scripts directory',
    },
    {
        name: 'config_env_substitution',
        source: 'file_context',
        applies: (finding) => {
            if (finding.category !== 'hardcoded_secret' && finding.category !== 'high_entropy_string') {
                return false;
            }
            return (0, file_classifier_1.isEnvVarReference)(finding.lineContent);
        },
        delta: -0.15,
        reason: 'Uses environment variable (not hardcoded)',
    },
    {
        name: 'comment_context',
        source: 'file_context',
        applies: (finding) => {
            if (finding.category === 'ai_pattern')
                return false;
            return (0, file_classifier_1.isComment)(finding.lineContent);
        },
        delta: -0.20,
        reason: 'Code comment (not executable)',
    },
    {
        name: 'server_only_client_finding',
        source: 'file_context',
        applies: (finding) => {
            if (finding.category !== 'xss')
                return false;
            return (0, file_classifier_1.isServerOnlyFile)(finding.filePath);
        },
        delta: -0.10,
        reason: 'XSS finding in server-only file',
    },
    {
        name: 'client_bundled_server_finding',
        source: 'file_context',
        applies: (finding, ctx) => {
            const serverOnlyCategories = [
                'missing_auth', 'missing_authorization',
                'data_exposure', 'sql_injection',
                'command_injection', 'ssrf',
            ];
            if (!serverOnlyCategories.includes(finding.category))
                return false;
            if (!ctx.file.isClientBundled)
                return false;
            // Exclude Next.js page files — App Router pages are server components
            // by default and Pages Router pages can have getServerSideProps.
            // Only penalise clearly client-only paths.
            const ambiguousPageFile = /\/pages\//i.test(finding.filePath) ||
                /\/app\/.*page\.(ts|tsx|js|jsx)$/i.test(finding.filePath);
            return !ambiguousPageFile;
        },
        delta: -0.30,
        reason: 'Server-side concern in client-bundled file (browser code)',
    },
];
exports.FILE_CONTEXT_RULES = FILE_CONTEXT_RULES;
// ============================================================================
// Auto-Dismiss Equivalent Rules (active now)
// Mirrors current score/auto-dismiss.ts behavior using confidence deltas
// ============================================================================
const AUTO_DISMISS_RULES = [
    {
        name: 'css_class_pattern',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'high_entropy_string')
                return false;
            const cssIndicators = ['flex', 'grid', 'text-', 'bg-', 'px-', 'py-', 'rounded', 'shadow', 'hover:', 'dark:'];
            const lowerLine = finding.lineContent.toLowerCase();
            const matchCount = cssIndicators.filter(ind => lowerLine.includes(ind)).length;
            return matchCount >= 2;
        },
        delta: -0.40,
        reason: 'CSS/Tailwind classes (not a secret)',
    },
    {
        name: 'health_check_endpoint',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'missing_auth')
                return false;
            return (0, file_classifier_1.isPublicEndpoint)(finding.lineContent, finding.filePath);
        },
        delta: -0.35,
        reason: 'Public health check endpoint (auth not required)',
    },
    {
        name: 'info_severity_low_priority',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.severity !== 'info')
                return false;
            // Use baseConfidence >= 0.35 instead of getTierForCategory
            // This replaces the tier-based 'info_severity_core_only' rule
            return (finding.baseConfidence ?? 0) >= 0.35;
        },
        delta: -0.15,
        reason: 'Already info severity for high-confidence detector (low priority)',
    },
    {
        name: 'low_severity_ai_pattern',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'ai_pattern')
                return false;
            return finding.severity === 'info' || finding.severity === 'low';
        },
        delta: -0.10,
        reason: 'Low-severity AI pattern (code quality, not security)',
    },
    {
        name: 'generic_message',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'ai_pattern')
                return false;
            const genericPatterns = [
                /['"`](success|done|ok|completed|finished|saved|updated|deleted|created)['"`]/i,
                /['"`]something went wrong['"`]/i,
                /['"`]an error occurred['"`]/i,
                /console\.(log|info|debug)\s*\(\s*['"`][^'"]+['"`]\s*\)/i,
            ];
            return genericPatterns.some(p => p.test(finding.lineContent));
        },
        delta: -0.25,
        reason: 'Generic UI message (not security-relevant)',
    },
    {
        name: 'type_definition_any',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'ai_pattern')
                return false;
            if (!finding.title.toLowerCase().includes('any'))
                return false;
            if (finding.filePath.includes('.d.ts'))
                return true;
            const typeDefPatterns = [/^type\s+\w+\s*=/, /^interface\s+\w+/, /declare\s+(const|let|var|function|class)/];
            return typeDefPatterns.some(p => p.test(finding.lineContent.trim()));
        },
        delta: -0.25,
        reason: 'Type definition (not runtime code)',
    },
    {
        name: 'timeout_magic_number',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'ai_pattern')
                return false;
            return /set(Timeout|Interval)\s*\([^,]+,\s*\d+\s*\)/.test(finding.lineContent);
        },
        delta: -0.20,
        reason: 'Timeout value (code style, not security)',
    },
    {
        name: 'data_config_file_secret',
        source: 'auto_dismiss',
        applies: (finding) => {
            if (finding.category !== 'high_entropy_string' && finding.category !== 'hardcoded_secret') {
                return false;
            }
            return /\.(yml|yaml|json|toml|ini|cfg)$/i.test(finding.filePath) &&
                !/\.(env|secret|credential)/i.test(finding.filePath);
        },
        delta: -0.15,
        reason: 'Secret finding in data/config file (often static content, not credentials)',
    },
];
exports.AUTO_DISMISS_RULES = AUTO_DISMISS_RULES;
// ============================================================================
// Future Context Engine Rules (empty arrays — structurally present)
// ============================================================================
/** Taint analysis rules — Context Engine Phase 1 */
const TAINT_RULES = [
    {
        name: 'confirmed_taint_unsanitised',
        source: 'taint',
        applies: (_finding, ctx) => ctx.taint?.confirmed === true && !ctx.taint.sanitised && ctx.taint.confidence !== 'low',
        delta: +0.35,
        reason: 'Confirmed taint path from user input (no sanitisation)',
    },
    {
        name: 'confirmed_taint_sanitised',
        source: 'taint',
        applies: (_finding, ctx) => ctx.taint?.confirmed === true && ctx.taint.sanitised === true,
        delta: +0.15,
        reason: 'Confirmed taint path from user input (sanitised)',
    },
    {
        name: 'no_taint_path',
        source: 'taint',
        applies: (finding, ctx) => {
            const sinkCategories = ['dangerous_function', 'sql_injection', 'xss', 'command_injection', 'ssrf', 'data_exposure'];
            if (!sinkCategories.includes(finding.category))
                return false;
            return ctx.taint !== undefined && ctx.taint.confirmed === false;
        },
        delta: -0.10,
        reason: 'No taint path found (static data reaches sink)',
    },
    {
        name: 'low_confidence_taint',
        source: 'taint',
        applies: (_finding, ctx) => ctx.taint?.confirmed === true && !ctx.taint.sanitised && ctx.taint.confidence === 'low',
        delta: +0.10,
        reason: 'Low-confidence taint path (long chain or indirect)',
    },
];
exports.TAINT_RULES = TAINT_RULES;
/** Auth-related categories where route auth context matters */
const AUTH_RELATED_CATEGORIES = new Set([
    'missing_auth', 'missing_authorization', 'data_exposure',
    'sql_injection', 'command_injection', 'ssrf',
    'xss', 'dangerous_function', 'xxe',
    'ai_unsafe_execution', 'ai_endpoint_unprotected', 'ai_rag_exfiltration',
]);
/** Route/auth context rules — Context Engine Phase 2 */
const ROUTE_RULES = [
    {
        name: 'no_auth_on_route',
        source: 'route',
        applies: (finding, ctx) => {
            if (!ctx.route)
                return false;
            if (!AUTH_RELATED_CATEGORIES.has(finding.category))
                return false;
            return !ctx.route.hasAuth && !ctx.route.isPublic;
        },
        delta: +0.15,
        reason: 'Route has no auth middleware and is not explicitly public',
    },
    {
        name: 'has_auth_on_route',
        source: 'route',
        applies: (_finding, ctx) => ctx.route?.hasAuth === true,
        delta: -0.15,
        reason: 'Route is protected by auth middleware',
    },
    {
        name: 'public_route',
        source: 'route',
        applies: (finding, ctx) => {
            if (finding.category !== 'missing_auth')
                return false;
            return ctx.route?.isPublic === true;
        },
        delta: -0.25,
        reason: 'Route is an explicitly public endpoint (health, status, etc.)',
    },
    {
        name: 'mutation_route',
        source: 'route',
        applies: (_finding, ctx) => {
            if (!ctx.route)
                return false;
            const mutationMethods = ['POST', 'PUT', 'DELETE', 'PATCH'];
            return ctx.route.methods.some(m => mutationMethods.includes(m));
        },
        delta: +0.05,
        reason: 'Route handles mutation methods (POST/PUT/DELETE/PATCH)',
    },
    {
        name: 'rate_limited_route',
        source: 'route',
        applies: (_finding, ctx) => ctx.route?.hasRateLimiting === true,
        delta: -0.05,
        reason: 'Route has rate limiting applied',
    },
];
exports.ROUTE_RULES = ROUTE_RULES;
/** Framework-specific rules — Context Engine Phase 5 */
const FRAMEWORK_RULES = [
    {
        name: 'framework_auto_protects',
        source: 'framework',
        applies: (_finding, ctx) => {
            if (!ctx.framework?.safePatternMatch)
                return false;
            // ORM patterns are handled by orm_safe_query — avoid stacking
            return !ctx.framework.safePatternMatch.id.startsWith('orm_');
        },
        delta: -0.30,
        reason: 'Framework auto-protects against this vulnerability type',
    },
    {
        name: 'framework_unsafe_pattern',
        source: 'framework',
        applies: (_finding, ctx) => ctx.framework?.unsafePatternMatch != null,
        delta: +0.10,
        reason: 'Finding uses a framework-unsafe pattern',
    },
    {
        name: 'orm_safe_query',
        source: 'framework',
        applies: (finding, ctx) => {
            if (finding.category !== 'sql_injection' && finding.category !== 'dangerous_function')
                return false;
            if (!ctx.framework?.orm)
                return false;
            return ctx.framework.safePatternMatch?.id.startsWith('orm_') ?? false;
        },
        delta: -0.25,
        reason: 'ORM parameterises this query pattern by default',
    },
];
exports.FRAMEWORK_RULES = FRAMEWORK_RULES;
// ============================================================================
// Combined Export
// ============================================================================
/** All active adjustment rules, evaluated in order */
exports.ALL_ADJUSTMENT_RULES = [
    ...FILE_CONTEXT_RULES,
    ...AUTO_DISMISS_RULES,
    ...TAINT_RULES,
    ...ROUTE_RULES,
    ...FRAMEWORK_RULES,
];
//# sourceMappingURL=adjustments.js.map