"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectTransactionDomainFindings = detectTransactionDomainFindings;
const path = __importStar(require("node:path"));
const node_fs_1 = require("node:fs");
const ts = __importStar(require("typescript"));
const moduleDiscovery_1 = require("./moduleDiscovery");
const archpilotIgnore_1 = require("./smartInit/archpilotIgnore");
const validationProfiling_1 = require("./validationProfiling");
const sourceScopeClassification_1 = require("./sourceScopeClassification");
const RuleIds = {
    TXN_BOUNDARY_TOO_BROAD: 'AP-TXN-001',
    TXN_IN_CONTROLLER: 'AP-TXN-002',
    TXN_NESTED_RISK: 'AP-TXN-003',
    TXN_LONG_RUNNING_WORKFLOW: 'AP-TXN-004',
    DOM_ANEMIC_MODEL: 'AP-DOM-001',
    DOM_AGGREGATE_BOUNDARY_VIOLATION: 'AP-DOM-002',
    DOM_OVERSIZED_BOUNDARY: 'AP-DOM-003',
    DOM_LOGIC_SCATTERED: 'AP-DOM-004',
    DOM_CROSS_AGGREGATE_MODIFICATION: 'AP-DOM-005',
};
const maxFileSizeBytes = 384 * 1024;
const supportedExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.java', '.cs', '.py', '.php', '.go']);
const ignoredDirectoryNames = new Set([
    '.git',
    '.archpilot',
    '.next',
    '.vscode-test',
    'node_modules',
    'dist',
    'build',
    'out',
    'coverage',
    'test',
    'tests',
    '__tests__',
    'fixtures',
    'smoke',
    'examples',
    'docs',
    'reports',
    'generated',
]);
const transactionEvidencePattern = /@Transactional\b|\b(?:transactional|transaction)\s*\(|\b(?:\$transaction|startTransaction|beginTransaction|commitTransaction|rollbackTransaction|TransactionTemplate|PlatformTransactionManager|transactionManager|QueryRunner|EntityManager|TransactionScope|BeginTransaction|BeginTransactionAsync|Commit|CommitAsync|Rollback|RollbackAsync|DB::transaction|beginTransaction|commit|rollBack|session\.begin|BeginTx|Begin|db\.Transaction)\b/iu;
const longRunningPattern = /\b(?:HttpService|httpClient|http_client|RestTemplate|WebClient|axios|fetch|Kafka|Rabbit|Sqs|Queue|Mailer|Mail::|Http::|mailClient|emailClient|sendEmail|send\w*Email|Sms|upload|putObject|sleep|setTimeout|retry)\b/iu;
const transactionBoundaryDecoratorPattern = /@(?:Transactional|Transaction)\b|\[(?:Transaction|Transactional)\b[^\]]*\]/u;
const explicitTransactionBoundaryPattern = /\b(?:\$transaction|DB::transaction)\s*\(|\b(?:knex|sequelize|typeorm|dataSource|connection|manager|transactionManager)\.transaction\s*\(|\b(?:this\.)?connection\.withTransaction\s*\(|\b(?:startTransaction|beginTransaction|BeginTransaction|BeginTransactionAsync|BeginTx)\s*\(|\bnew\s+TransactionScope\b|\bsession\.begin\s*\(|\bdb\.Transaction\s*\(/iu;
const separateTransactionPropagationPattern = /\b(?:requiresNew|REQUIRES_NEW|Propagation\.REQUIRES_NEW)\b/u;
const businessRulePattern = /\b(?:approve|reject|authorize|eligible|eligibility|policy|discount|limit|quota|balance|risk|compliance|settle|state|status|transition)\b/iu;
const transactionCallbackStartPattern = /\b(?:this\.)?(?:[\w.$:]+\.)?(?:\$transaction|transaction|Transaction)\s*\(\s*(?:async\s*)?(?:function\s*)?\(?\s*\w*\s*\)?\s*(?:=>)?\s*\{/gu;
const explicitDomainModelEvidencePattern = /@(?:Entity|Embeddable|MappedSuperclass|Column|PrimaryGeneratedColumn|ObjectType|Field)\b|\[(?:Table|Key|Required|Column|StringLength|MaxLength)\b[^\]]*\]|\b(?:BaseModel|SQLModel|declarative_base|Mapped\[|Column\s*\(|@dataclass\b|extends\s+Model\b|\$fillable\b|\$casts\b|gorm\.Model|`[^`]*(?:gorm|db):)/u;
const domainFrameworkLeakPattern = /\b(?:ControllerBase|IActionResult|HttpContext|Microsoft\.AspNetCore|APIRouter|FastAPI|Request|Response|Depends|Illuminate\\Http|Illuminate\\Routing|Route::|http\.ResponseWriter|http\.Request|gin\.Context|express|Router|org\.springframework\.web|jakarta\.servlet|HttpServletRequest|@Controller|@RestController|@Get|@Post|DbContext|EntityManager|Session|Repository)\b/u;
function maskNonCodeEvidence(value) {
    let masked = '';
    let index = 0;
    let previousSignificant = '';
    const pushMasked = (count) => {
        masked += ' '.repeat(count);
    };
    const pushCode = (character) => {
        masked += character;
        if (!/\s/u.test(character)) {
            previousSignificant = character;
        }
    };
    while (index < value.length) {
        const character = value[index] ?? '';
        const next = value[index + 1] ?? '';
        if (character === '/' && next === '/') {
            const end = value.indexOf('\n', index + 2);
            const length = end === -1 ? value.length - index : end - index;
            pushMasked(length);
            index += length;
            continue;
        }
        if (character === '/' && next === '*') {
            const end = value.indexOf('*/', index + 2);
            const length = end === -1 ? value.length - index : end + 2 - index;
            pushMasked(length);
            index += length;
            continue;
        }
        if (character === '\'' || character === '"' || character === '`') {
            const quote = character;
            let cursor = index + 1;
            while (cursor < value.length) {
                const current = value[cursor] ?? '';
                if (current === '\\') {
                    cursor += 2;
                    continue;
                }
                if (current === quote) {
                    cursor += 1;
                    break;
                }
                cursor += 1;
            }
            pushMasked(cursor - index);
            index = cursor;
            continue;
        }
        if (character === '/' &&
            (previousSignificant === '' ||
                /[([{:;,=!?&|]/u.test(previousSignificant) ||
                /\b(?:return|case|throw|yield)\s*$/u.test(value.slice(Math.max(0, index - 24), index)))) {
            let cursor = index + 1;
            let inCharacterClass = false;
            while (cursor < value.length) {
                const current = value[cursor] ?? '';
                if (current === '\\') {
                    cursor += 2;
                    continue;
                }
                if (current === '[') {
                    inCharacterClass = true;
                }
                else if (current === ']') {
                    inCharacterClass = false;
                }
                else if (current === '/' && !inCharacterClass) {
                    cursor += 1;
                    while (/[a-z]/iu.test(value[cursor] ?? '')) {
                        cursor += 1;
                    }
                    break;
                }
                cursor += 1;
            }
            pushMasked(cursor - index);
            index = cursor;
            continue;
        }
        pushCode(character);
        index += 1;
    }
    return masked;
}
function normalizePath(value) {
    return value.replace(/\\/g, '/');
}
function toPosixRelative(workspaceRoot, absolutePath) {
    return normalizePath(path.relative(workspaceRoot, absolutePath));
}
async function readTextFileIfReasonable(filePath, sourceInventory) {
    if (sourceInventory) {
        return sourceInventory.readText(filePath, { maxFileSizeBytes });
    }
    try {
        const stats = await node_fs_1.promises.stat(filePath);
        if (stats.size > maxFileSizeBytes) {
            return undefined;
        }
        return await node_fs_1.promises.readFile(filePath, { encoding: 'utf8' });
    }
    catch {
        return undefined;
    }
}
async function collectSourceFiles(workspaceRoot, sourceInventory) {
    if (sourceInventory) {
        const files = await sourceInventory.getTextFiles({
            extensions: [...supportedExtensions],
            ignoredDirectoryNames: [...ignoredDirectoryNames],
            maxFileSizeBytes,
        });
        return files.map((file) => ({
            relativePath: file.relativePath,
            content: file.content,
            extension: file.extension,
        }));
    }
    const ignoreMatcher = (0, archpilotIgnore_1.loadArchpilotIgnoreMatcher)(workspaceRoot);
    const collected = [];
    const stack = [workspaceRoot];
    while (stack.length > 0) {
        const current = stack.pop();
        if (!current) {
            continue;
        }
        let entries;
        try {
            entries = await node_fs_1.promises.readdir(current, { withFileTypes: true });
        }
        catch {
            continue;
        }
        for (const entry of entries) {
            const absoluteEntryPath = path.join(current, entry.name);
            const relativeEntryPath = toPosixRelative(workspaceRoot, absoluteEntryPath);
            if (ignoreMatcher.isIgnored(relativeEntryPath)) {
                continue;
            }
            if (entry.isDirectory()) {
                if (entry.name.startsWith('.') || ignoredDirectoryNames.has(entry.name)) {
                    continue;
                }
                stack.push(absoluteEntryPath);
                continue;
            }
            const extension = path.extname(entry.name).toLowerCase();
            if (!supportedExtensions.has(extension)) {
                continue;
            }
            const content = await readTextFileIfReasonable(absoluteEntryPath, sourceInventory);
            if (content === undefined) {
                continue;
            }
            collected.push({ relativePath: relativeEntryPath, content, extension });
        }
    }
    return collected.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
}
function lineNumberForOffset(content, offset) {
    let line = 1;
    for (let index = 0; index < offset && index < content.length; index += 1) {
        if (content[index] === '\n') {
            line += 1;
        }
    }
    return line;
}
function getLineStarts(file) {
    if (file.lineStarts) {
        return file.lineStarts;
    }
    const lineStarts = [0];
    for (let index = 0; index < file.content.length; index += 1) {
        if (file.content[index] === '\n') {
            lineStarts.push(index + 1);
        }
    }
    file.lineStarts = lineStarts;
    return lineStarts;
}
function lineNumberForFileOffset(file, offset) {
    const lineStarts = getLineStarts(file);
    let low = 0;
    let high = lineStarts.length - 1;
    while (low <= high) {
        const middle = Math.floor((low + high) / 2);
        const lineStart = lineStarts[middle] ?? 0;
        if (lineStart <= offset) {
            low = middle + 1;
        }
        else {
            high = middle - 1;
        }
    }
    return Math.max(1, high + 1);
}
function findMatchingBrace(content, openBraceIndex) {
    let depth = 0;
    for (let index = openBraceIndex; index < content.length; index += 1) {
        const char = content[index];
        if (char === '{') {
            depth += 1;
        }
        else if (char === '}') {
            depth -= 1;
            if (depth === 0) {
                return index;
            }
        }
    }
    return Math.min(content.length - 1, openBraceIndex + 5000);
}
function createMethodEntry(input) {
    let cachedBody;
    return {
        name: input.name,
        ...(input.className ? { className: input.className } : {}),
        decorators: input.decorators,
        bodyStartOffset: input.bodyStartOffset,
        bodyEndOffset: input.bodyEndOffset,
        line: lineNumberForFileOffset(input.file, input.lineOffset),
        file: input.file,
        classification: input.classification,
        get body() {
            cachedBody ??= input.file.content.slice(input.bodyStartOffset, input.bodyEndOffset);
            return cachedBody;
        },
    };
}
function extractTransactionCallbackBodies(body) {
    const callbackBodies = [];
    for (const match of body.matchAll(transactionCallbackStartPattern)) {
        const openBraceIndex = (match.index ?? 0) + match[0].lastIndexOf('{');
        const closeBraceIndex = findMatchingBrace(body, openBraceIndex);
        callbackBodies.push(body.slice(openBraceIndex, closeBraceIndex + 1));
    }
    return callbackBodies;
}
function inferModuleForPath(contract, relativePath) {
    const normalized = normalizePath(relativePath);
    const modulesRoot = (0, moduleDiscovery_1.resolveModulesRootFromContract)(contract);
    for (const [moduleName, reference] of Object.entries(contract.modules ?? {})) {
        const modulePath = normalizePath(reference.path ?? `${modulesRoot}/${moduleName}`);
        if (normalized === modulePath || normalized.startsWith(`${modulePath}/`)) {
            return moduleName;
        }
    }
    return undefined;
}
function parseSourceFile(file, astCache) {
    return astCache
        ? astCache.parseSourceFile({
            filePath: file.relativePath,
            sourceText: file.content,
            scriptTarget: ts.ScriptTarget.Latest,
            setParentNodes: true,
        })
        : ts.createSourceFile(file.relativePath, file.content, ts.ScriptTarget.Latest, true);
}
function getTypeScriptDeclaredRoles(file, astCache) {
    const sourceFile = parseSourceFile(file, astCache);
    const roles = new Set();
    if (explicitDomainModelEvidencePattern.test(maskNonCodeEvidence(file.content))) {
        roles.add('entity');
    }
    const visit = (node) => {
        if ((ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.name) {
            const name = node.name.text;
            if (/Controller$/u.test(name)) {
                roles.add('controller');
            }
            if (/Service$/u.test(name)) {
                roles.add('service');
            }
            if (/(Repository|Dao)$/u.test(name)) {
                roles.add('repository');
            }
            if (/(Entity|Aggregate|Model)$/u.test(name)) {
                roles.add('entity');
            }
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    return [...roles];
}
function getDeclaredRoles(file, astCache) {
    if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(file.extension)) {
        return getTypeScriptDeclaredRoles(file, astCache);
    }
    const code = maskNonCodeEvidence(file.content);
    const roles = new Set();
    if (/\bclass\s+\w*Controller\b/u.test(code) || /@(?:Controller|RestController)\b|\[ApiController\]/u.test(code)) {
        roles.add('controller');
    }
    if (/\bclass\s+\w*Service\b/u.test(code) || /@Service\b/u.test(code)) {
        roles.add('service');
    }
    if (/\bclass\s+\w*(?:Repository|Dao)\b/u.test(code) || /@Repository\b/u.test(code)) {
        roles.add('repository');
    }
    if (/\bclass\s+\w*(?:Entity|Aggregate|Model)\b/u.test(code) || explicitDomainModelEvidencePattern.test(code)) {
        roles.add('entity');
    }
    return [...roles];
}
function classifySourceFile(file, contract, astCache) {
    const scope = (0, sourceScopeClassification_1.classifyValidationSourceScope)(file.relativePath);
    const roles = new Set(getDeclaredRoles(file, astCache));
    const lowerPath = file.relativePath.toLowerCase();
    if (/(^|[./_-])controller\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/u.test(lowerPath) || /(^|\/)(controllers?|routers?|routes?|handlers?)\//u.test(lowerPath)) {
        roles.add('controller');
    }
    if (/(^|[./_-])service\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/u.test(lowerPath) || /(^|\/)(services?)\//u.test(lowerPath)) {
        roles.add('service');
    }
    if (/(^|[./_-])(?:repository|repo|dao)\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/u.test(lowerPath) || /(^|\/)(repositories?|repository|dao|data)\//u.test(lowerPath)) {
        roles.add('repository');
    }
    if (/(^|\/)(?:domain|entities|entity|models)\//u.test(lowerPath) && !/(dto|request|response|viewmodel)/u.test(lowerPath)) {
        roles.add('entity');
    }
    return {
        roles: [...roles],
        moduleName: inferModuleForPath(contract, file.relativePath),
        scope,
    };
}
function getNodeNameText(name) {
    if (!name) {
        return undefined;
    }
    if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) {
        return name.text;
    }
    if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
        return name.text;
    }
    return undefined;
}
function createTypeScriptMethodEntry(input) {
    const bodyStartOffset = input.body.getStart(input.sourceFile, false);
    const bodyEndOffset = input.body.getEnd();
    return createMethodEntry({
        name: input.name,
        className: input.className,
        decorators: input.file.content.slice(input.node.getFullStart(), bodyStartOffset),
        bodyStartOffset,
        bodyEndOffset,
        lineOffset: input.node.getStart(input.sourceFile, false),
        file: input.file,
        classification: input.classification,
    });
}
function extractTypeScriptMethods(file, classification, astCache) {
    const sourceFile = parseSourceFile(file, astCache);
    const methods = [];
    const visit = (node, className) => {
        if (ts.isClassDeclaration(node)) {
            const currentClassName = node.name?.text ?? className;
            ts.forEachChild(node, (child) => visit(child, currentClassName));
            return;
        }
        if (ts.isMethodDeclaration(node) && node.body) {
            methods.push(createTypeScriptMethodEntry({
                file,
                sourceFile,
                node,
                name: getNodeNameText(node.name) ?? 'method',
                className,
                body: node.body,
                classification,
            }));
            ts.forEachChild(node.body, (child) => visit(child, className));
            return;
        }
        if (ts.isConstructorDeclaration(node) && node.body) {
            methods.push(createTypeScriptMethodEntry({
                file,
                sourceFile,
                node,
                name: 'constructor',
                className,
                body: node.body,
                classification,
            }));
            ts.forEachChild(node.body, (child) => visit(child, className));
            return;
        }
        if (ts.isPropertyDeclaration(node) &&
            node.initializer &&
            (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&
            node.initializer.body) {
            methods.push(createTypeScriptMethodEntry({
                file,
                sourceFile,
                node,
                name: getNodeNameText(node.name) ?? 'method',
                className,
                body: node.initializer.body,
                classification,
            }));
            ts.forEachChild(node.initializer.body, (child) => visit(child, className));
            return;
        }
        if (ts.isFunctionDeclaration(node) && node.body) {
            methods.push(createTypeScriptMethodEntry({
                file,
                sourceFile,
                node,
                name: node.name?.text ?? 'function',
                className,
                body: node.body,
                classification,
            }));
            ts.forEachChild(node.body, (child) => visit(child, className));
            return;
        }
        if (ts.isVariableDeclaration(node) &&
            node.initializer &&
            (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&
            node.initializer.body) {
            methods.push(createTypeScriptMethodEntry({
                file,
                sourceFile,
                node,
                name: getNodeNameText(node.name) ?? 'function',
                className,
                body: node.initializer.body,
                classification,
            }));
            ts.forEachChild(node.initializer.body, (child) => visit(child, className));
            return;
        }
        ts.forEachChild(node, (child) => visit(child, className));
    };
    visit(sourceFile);
    return methods;
}
function getLineEndOffset(content, lineStart) {
    const newlineOffset = content.indexOf('\n', lineStart);
    return newlineOffset === -1 ? content.length : newlineOffset;
}
function countJavaAnnotationParenDelta(line) {
    let delta = 0;
    for (const char of line) {
        if (char === '(') {
            delta += 1;
        }
        else if (char === ')') {
            delta -= 1;
        }
    }
    return delta;
}
function findJavaOpeningBrace(file, startLineIndex, maxSignatureLines = 30) {
    const lineStarts = getLineStarts(file);
    const signatureLines = [];
    for (let lineIndex = startLineIndex; lineIndex < lineStarts.length && lineIndex < startLineIndex + maxSignatureLines; lineIndex += 1) {
        const lineStart = lineStarts[lineIndex] ?? 0;
        const lineEnd = getLineEndOffset(file.content, lineStart);
        const line = file.content.slice(lineStart, lineEnd);
        signatureLines.push(line);
        const openBraceColumn = line.indexOf('{');
        const semicolonColumn = line.indexOf(';');
        if (openBraceColumn >= 0 && (semicolonColumn < 0 || openBraceColumn < semicolonColumn)) {
            return {
                openBraceOffset: lineStart + openBraceColumn,
                signature: signatureLines.join('\n'),
                endLineIndex: lineIndex,
            };
        }
        if (semicolonColumn >= 0) {
            return undefined;
        }
    }
    return undefined;
}
function extractJavaMethodName(signature, currentClassName) {
    const beforeParameters = signature.slice(0, signature.indexOf('(')).replace(/<[^<>]*(?:<[^<>]*>[^<>]*)*>/gu, ' ');
    const nameMatch = /([A-Za-z_$][\w$]*)\s*$/u.exec(beforeParameters);
    const name = nameMatch?.[1];
    if (!name) {
        return undefined;
    }
    if (['if', 'for', 'while', 'switch', 'catch', 'new', 'return'].includes(name)) {
        return undefined;
    }
    if (currentClassName && name === currentClassName) {
        return name;
    }
    if (!/\b(?:public|private|protected)\b/u.test(beforeParameters)) {
        return undefined;
    }
    return name;
}
function isJavaMethodSignatureCandidate(line) {
    const trimmed = line.trimStart();
    if (!/^(?:public|private|protected)\b/u.test(trimmed)) {
        return false;
    }
    if (/\b(?:class|interface|enum|record)\b/u.test(trimmed)) {
        return false;
    }
    if (!trimmed.includes('(')) {
        return false;
    }
    return true;
}
function extractJavaMethods(file, classification) {
    const methods = [];
    const lineStarts = getLineStarts(file);
    let pendingAnnotationStart;
    let pendingAnnotationEnd;
    let annotationParenDepth = 0;
    let currentClassName;
    for (let lineIndex = 0; lineIndex < lineStarts.length; lineIndex += 1) {
        const lineStart = lineStarts[lineIndex] ?? 0;
        const lineEnd = getLineEndOffset(file.content, lineStart);
        const line = file.content.slice(lineStart, lineEnd);
        const trimmed = line.trimStart();
        const classMatch = /\b(?:class|interface|enum|record)\s+([A-Za-z_$][\w$]*)\b/u.exec(line);
        if (classMatch?.[1]) {
            currentClassName = classMatch[1];
        }
        if (trimmed.startsWith('@') || annotationParenDepth > 0) {
            pendingAnnotationStart ??= lineStart;
            pendingAnnotationEnd = lineEnd;
            annotationParenDepth = Math.max(0, annotationParenDepth + countJavaAnnotationParenDelta(line));
            continue;
        }
        if (trimmed.length === 0) {
            continue;
        }
        if (!isJavaMethodSignatureCandidate(line)) {
            pendingAnnotationStart = undefined;
            pendingAnnotationEnd = undefined;
            continue;
        }
        const openingBrace = findJavaOpeningBrace(file, lineIndex);
        if (!openingBrace) {
            pendingAnnotationStart = undefined;
            pendingAnnotationEnd = undefined;
            continue;
        }
        const methodName = extractJavaMethodName(openingBrace.signature, currentClassName);
        if (!methodName) {
            pendingAnnotationStart = undefined;
            pendingAnnotationEnd = undefined;
            continue;
        }
        const end = findMatchingBrace(file.content, openingBrace.openBraceOffset);
        methods.push(createMethodEntry({
            name: methodName,
            ...(currentClassName ? { className: currentClassName } : {}),
            decorators: pendingAnnotationStart === undefined || pendingAnnotationEnd === undefined
                ? ''
                : file.content.slice(pendingAnnotationStart, pendingAnnotationEnd),
            bodyStartOffset: openingBrace.openBraceOffset,
            bodyEndOffset: end + 1,
            lineOffset: lineStart,
            file,
            classification,
        }));
        pendingAnnotationStart = undefined;
        pendingAnnotationEnd = undefined;
        lineIndex = openingBrace.endLineIndex;
    }
    return methods;
}
function isBraceMethodSignatureCandidate(line, extension) {
    const trimmed = line.trimStart();
    if (extension === '.cs') {
        return /^(?:\[[^\]]+\]\s*)*(?:public|private|protected|internal)\b/u.test(trimmed) &&
            trimmed.includes('(') &&
            !/\b(?:class|interface|enum|record)\b/u.test(trimmed);
    }
    if (extension === '.php') {
        return /^(?:(?:public|private|protected|static|final|abstract)\s+)*function\s+\w+\s*\(/u.test(trimmed);
    }
    if (extension === '.go') {
        return /^func\s+(?:\([^)]*\)\s*)?[A-Za-z_]\w*\s*\(/u.test(trimmed);
    }
    return false;
}
function extractBraceMethodName(signature, extension) {
    const trimmed = signature.trimStart();
    if (extension === '.php') {
        return /function\s+(\w+)\s*\(/u.exec(trimmed)?.[1];
    }
    if (extension === '.go') {
        return /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/u.exec(trimmed)?.[1];
    }
    const beforeParameters = signature.slice(0, signature.indexOf('('));
    const name = /([A-Za-z_]\w*)\s*$/u.exec(beforeParameters)?.[1];
    return name && !['if', 'for', 'while', 'switch', 'catch', 'using', 'return'].includes(name) ? name : undefined;
}
function extractBraceMethods(file, classification) {
    const methods = [];
    const lineStarts = getLineStarts(file);
    let pendingDecoratorStart;
    let pendingDecoratorEnd;
    let currentClassName;
    for (let lineIndex = 0; lineIndex < lineStarts.length; lineIndex += 1) {
        const lineStart = lineStarts[lineIndex] ?? 0;
        const lineEnd = getLineEndOffset(file.content, lineStart);
        const line = file.content.slice(lineStart, lineEnd);
        const trimmed = line.trimStart();
        const classMatch = /\bclass\s+([A-Za-z_]\w*)\b/u.exec(line);
        if (classMatch?.[1]) {
            currentClassName = classMatch[1];
        }
        if (/^(?:\[|#\[|@)/u.test(trimmed)) {
            pendingDecoratorStart ??= lineStart;
            pendingDecoratorEnd = lineEnd;
            continue;
        }
        if (trimmed.length === 0) {
            continue;
        }
        if (!isBraceMethodSignatureCandidate(line, file.extension)) {
            pendingDecoratorStart = undefined;
            pendingDecoratorEnd = undefined;
            continue;
        }
        const openingBrace = findJavaOpeningBrace(file, lineIndex, 12);
        if (!openingBrace) {
            pendingDecoratorStart = undefined;
            pendingDecoratorEnd = undefined;
            continue;
        }
        const methodName = extractBraceMethodName(openingBrace.signature, file.extension);
        if (!methodName) {
            pendingDecoratorStart = undefined;
            pendingDecoratorEnd = undefined;
            continue;
        }
        const end = findMatchingBrace(file.content, openingBrace.openBraceOffset);
        methods.push(createMethodEntry({
            name: methodName,
            ...(currentClassName ? { className: currentClassName } : {}),
            decorators: pendingDecoratorStart === undefined || pendingDecoratorEnd === undefined
                ? ''
                : file.content.slice(pendingDecoratorStart, pendingDecoratorEnd),
            bodyStartOffset: openingBrace.openBraceOffset,
            bodyEndOffset: end + 1,
            lineOffset: lineStart,
            file,
            classification,
        }));
        pendingDecoratorStart = undefined;
        pendingDecoratorEnd = undefined;
        lineIndex = openingBrace.endLineIndex;
    }
    return methods;
}
function leadingWhitespaceLength(value) {
    return value.length - value.trimStart().length;
}
function extractPythonMethods(file, classification) {
    const methods = [];
    const lineStarts = getLineStarts(file);
    let pendingDecoratorStart;
    let pendingDecoratorEnd;
    let currentClassName;
    for (let lineIndex = 0; lineIndex < lineStarts.length; lineIndex += 1) {
        const lineStart = lineStarts[lineIndex] ?? 0;
        const lineEnd = getLineEndOffset(file.content, lineStart);
        const line = file.content.slice(lineStart, lineEnd);
        const trimmed = line.trimStart();
        const classMatch = /^class\s+([A-Za-z_]\w*)\b/u.exec(trimmed);
        if (classMatch?.[1]) {
            currentClassName = classMatch[1];
        }
        if (trimmed.startsWith('@')) {
            pendingDecoratorStart ??= lineStart;
            pendingDecoratorEnd = lineEnd;
            continue;
        }
        const methodMatch = /^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\([^)]*\)\s*:/u.exec(trimmed);
        if (!methodMatch?.[1]) {
            if (trimmed.length > 0) {
                pendingDecoratorStart = undefined;
                pendingDecoratorEnd = undefined;
            }
            continue;
        }
        const definitionIndent = leadingWhitespaceLength(line);
        let bodyEndOffset = file.content.length;
        for (let cursor = lineIndex + 1; cursor < lineStarts.length; cursor += 1) {
            const cursorStart = lineStarts[cursor] ?? 0;
            const cursorEnd = getLineEndOffset(file.content, cursorStart);
            const cursorLine = file.content.slice(cursorStart, cursorEnd);
            if (cursorLine.trim().length === 0) {
                continue;
            }
            if (leadingWhitespaceLength(cursorLine) <= definitionIndent) {
                bodyEndOffset = cursorStart;
                break;
            }
        }
        methods.push(createMethodEntry({
            name: methodMatch[1],
            ...(currentClassName ? { className: currentClassName } : {}),
            decorators: pendingDecoratorStart === undefined || pendingDecoratorEnd === undefined
                ? ''
                : file.content.slice(pendingDecoratorStart, pendingDecoratorEnd),
            bodyStartOffset: lineStart,
            bodyEndOffset,
            lineOffset: lineStart,
            file,
            classification,
        }));
        pendingDecoratorStart = undefined;
        pendingDecoratorEnd = undefined;
    }
    return methods;
}
function extractMethods(file, classification, astCache) {
    if (file.extension === '.java') {
        return extractJavaMethods(file, classification);
    }
    if (file.extension === '.py') {
        return extractPythonMethods(file, classification);
    }
    if (file.extension === '.cs' || file.extension === '.php' || file.extension === '.go') {
        return extractBraceMethods(file, classification);
    }
    return extractTypeScriptMethods(file, classification, astCache);
}
function emitSlowTransactionDomainFileProfile(label, file, durationMs, details) {
    if (durationMs < 100) {
        return;
    }
    (0, validationProfiling_1.emitValidationProfile)(`AP-TXN/AP-DOM ${label} ${file.relativePath}`, durationMs, details);
}
function createFinding(input) {
    return {
        id: input.id,
        severity: 'warning',
        passed: false,
        message: input.message,
        findingType: 'file-scope',
        filePath: input.file.relativePath,
        sourceFile: input.file.relativePath,
        ...(input.line ? { sourceLine: input.line } : {}),
        ...(input.module ? { module: input.module } : {}),
        ...(input.target ? { target: input.target } : {}),
    };
}
function hasRole(classification, role) {
    return classification.roles.includes(role);
}
function hasExplicitDomainModelEvidence(file) {
    const code = maskNonCodeEvidence(file.content);
    return explicitDomainModelEvidencePattern.test(code) || /`[^`]*(?:gorm|db):/u.test(file.content);
}
function hasDomainFrameworkLeakEvidence(file) {
    return domainFrameworkLeakPattern.test(maskNonCodeEvidence(file.content));
}
function hasTransactionEvidence(method) {
    return transactionEvidencePattern.test(method.decorators) || transactionEvidencePattern.test(maskNonCodeEvidence(method.body));
}
function hasTransactionBoundaryDecorator(method) {
    return transactionBoundaryDecoratorPattern.test(method.decorators);
}
function hasExplicitTransactionBoundaryCreation(method) {
    return explicitTransactionBoundaryPattern.test(maskNonCodeEvidence(method.body));
}
function isApplicationTransactionScope(method) {
    return hasTransactionBoundaryDecorator(method) || hasExplicitTransactionBoundaryCreation(method);
}
function isApplicationTransactionRuleScope(method) {
    return (0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(method.classification.scope);
}
function getTransactionBoundaryMethodKey(method) {
    if (!method.className || !hasTransactionBoundaryDecorator(method)) {
        return undefined;
    }
    return `${method.file.relativePath}#${method.className}.${method.name}`;
}
function callsKnownLocalTransactionBoundaryMethod(method, transactionBoundaryMethodKeys) {
    if (!method.className) {
        return false;
    }
    const codeBody = maskNonCodeEvidence(method.body);
    for (const key of transactionBoundaryMethodKeys) {
        const prefix = `${method.file.relativePath}#${method.className}.`;
        if (!key.startsWith(prefix)) {
            continue;
        }
        const name = key.slice(prefix.length);
        if (name === method.name) {
            continue;
        }
        if (new RegExp(`\\bthis\\.${name}\\s*\\(`, 'u').test(codeBody)) {
            return true;
        }
    }
    return false;
}
function hasNestedTransactionBoundaryEvidence(method, transactionBoundaryMethodKeys) {
    const codeBody = maskNonCodeEvidence(method.body);
    return (separateTransactionPropagationPattern.test(codeBody) ||
        (hasTransactionBoundaryDecorator(method) && hasExplicitTransactionBoundaryCreation(method)) ||
        callsKnownLocalTransactionBoundaryMethod(method, transactionBoundaryMethodKeys));
}
function isConstructorEntry(method) {
    return method.name === 'constructor' || (!!method.className && method.name === method.className);
}
function hasExplicitConstructorTransactionEvidence(method) {
    return isConstructorEntry(method) && transactionEvidencePattern.test(method.decorators);
}
function hasLongRunningTransactionWorkflowEvidence(method) {
    const codeBody = maskNonCodeEvidence(method.body);
    if (hasTransactionBoundaryDecorator(method) && longRunningPattern.test(codeBody)) {
        return true;
    }
    if (hasExplicitTransactionBoundaryCreation(method) && longRunningPattern.test(codeBody)) {
        return true;
    }
    const callbackBodies = extractTransactionCallbackBodies(codeBody);
    return callbackBodies.some((callbackBody) => longRunningPattern.test(callbackBody));
}
function extractRepositoryStems(value) {
    const stems = new Set();
    for (const match of value.matchAll(/\b(?:this\.)?(\w+?)(?:Repository|Repo|Dao)\b/gu)) {
        const stem = match[1];
        if (stem && !/^(base|generic|crud)$/iu.test(stem)) {
            stems.add(stem.toLowerCase());
        }
    }
    return [...stems];
}
function extractMutatedRepositoryStems(value) {
    const stems = new Set();
    const pattern = /\b(?:this\.)?(\w+?)(?:Repository|Repo|Dao)\.(\w+)\s*\(/gu;
    for (const match of value.matchAll(pattern)) {
        const operation = match[2] ?? '';
        if (/^(save|create|insert|update|delete|remove|persist|merge)/iu.test(operation)) {
            const stem = match[1];
            if (stem) {
                stems.add(stem.toLowerCase());
            }
        }
    }
    return [...stems];
}
function mapStemToModule(stem, contract) {
    const normalizedStem = stem.replace(/[^a-z0-9]/giu, '').toLowerCase();
    for (const moduleName of Object.keys(contract.modules ?? {})) {
        const normalizedModule = moduleName.replace(/[^a-z0-9]/giu, '').toLowerCase();
        if (normalizedModule === normalizedStem || normalizedModule.endsWith(normalizedStem) || normalizedStem.endsWith(normalizedModule)) {
            return moduleName;
        }
    }
    return undefined;
}
function isCrossAggregateModificationRisk(method, contract) {
    const mutatedStems = extractMutatedRepositoryStems(method.body);
    const mutatedModules = new Set(mutatedStems.map((stem) => mapStemToModule(stem, contract)).filter(Boolean));
    return (hasTransactionEvidence(method) &&
        hasRole(method.classification, 'service') &&
        (mutatedStems.length >= 3 || mutatedModules.size >= 2));
}
function detectTransactionFindings(methods, contract) {
    const transactionBoundaryMethodKeys = new Set(methods.map(getTransactionBoundaryMethodKey).filter((key) => !!key));
    const findings = [];
    for (const method of methods) {
        const isTransactional = hasTransactionEvidence(method);
        if (!isTransactional) {
            continue;
        }
        if (!isApplicationTransactionRuleScope(method)) {
            continue;
        }
        const isConstructor = isConstructorEntry(method);
        if (isConstructor && !hasExplicitConstructorTransactionEvidence(method)) {
            continue;
        }
        if (hasRole(method.classification, 'controller')) {
            findings.push(createFinding({
                id: RuleIds.TXN_IN_CONTROLLER,
                message: `Endpoint/controller method '${method.name}' appears to own a transaction boundary.`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
        const repositoryStems = extractRepositoryStems(method.body);
        if (!isConstructor &&
            hasRole(method.classification, 'service') &&
            (repositoryStems.length >= 4 || (repositoryStems.length >= 3 && method.body.split(/\r?\n/u).length >= 80))) {
            findings.push(createFinding({
                id: RuleIds.TXN_BOUNDARY_TOO_BROAD,
                message: `Transactional method '${method.name}' coordinates many repository/aggregate collaborators (${repositoryStems.join(', ')}).`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
        if (!isConstructor &&
            isApplicationTransactionScope(method) &&
            hasNestedTransactionBoundaryEvidence(method, transactionBoundaryMethodKeys)) {
            findings.push(createFinding({
                id: RuleIds.TXN_NESTED_RISK,
                message: `Transactional method '${method.name}' appears to start or call another transaction scope.`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
        if (!isConstructor && hasLongRunningTransactionWorkflowEvidence(method)) {
            findings.push(createFinding({
                id: RuleIds.TXN_LONG_RUNNING_WORKFLOW,
                message: `Transactional method '${method.name}' contains external I/O or long-running workflow evidence.`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
        const mutatedStems = extractMutatedRepositoryStems(method.body);
        if (!isConstructor && isCrossAggregateModificationRisk(method, contract)) {
            findings.push(createFinding({
                id: RuleIds.DOM_CROSS_AGGREGATE_MODIFICATION,
                message: `Transactional method '${method.name}' mutates multiple aggregate/repository roots (${mutatedStems.join(', ')}).`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
    }
    return findings;
}
function countTypeScriptClassMembers(file, astCache) {
    const sourceFile = parseSourceFile(file, astCache);
    let fields = 0;
    let methods = 0;
    const visit = (node) => {
        if (ts.isClassDeclaration(node)) {
            for (const member of node.members) {
                if (ts.isPropertyDeclaration(member)) {
                    fields += 1;
                }
                if (ts.isMethodDeclaration(member)) {
                    methods += 1;
                }
            }
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    return { fields, methods };
}
function countJavaClassMembers(content) {
    return {
        fields: [...content.matchAll(/\b(?:private|protected|public)\s+(?!class\b)[\w<>, ?\[\]]+\s+\w+\s*;/gu)].length,
        methods: [...content.matchAll(/\b(?:public|private|protected)\s+(?!class\b)[\w<>, ?\[\]]+\s+\w+\s*\([^)]*\)\s*\{/gu)].length,
    };
}
function countDomainModelMembers(file, astCache) {
    if (file.extension === '.java') {
        return countJavaClassMembers(file.content);
    }
    if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(file.extension)) {
        return countTypeScriptClassMembers(file, astCache);
    }
    if (file.extension === '.cs') {
        return {
            fields: [...file.content.matchAll(/\b(?:public|private|protected|internal)\s+[\w<>?\[\]]+\s+\w+\s*(?:\{\s*get;|\s*[;=])/gu)].length,
            methods: [...file.content.matchAll(/\b(?:public|private|protected|internal)\s+[\w<>?\[\]]+\s+\w+\s*\([^)]*\)\s*\{/gu)].length,
        };
    }
    if (file.extension === '.py') {
        return {
            fields: [...file.content.matchAll(/^\s{4}[A-Za-z_]\w*\s*:/gmu)].length,
            methods: [...file.content.matchAll(/^\s{4}(?:async\s+)?def\s+\w+\s*\(/gmu)].length,
        };
    }
    if (file.extension === '.php') {
        const fillableMatch = /\$fillable\s*=\s*\[([\s\S]*?)\]/u.exec(file.content);
        const fillableFields = fillableMatch?.[1]
            ? [...fillableMatch[1].matchAll(/['"][^'"]+['"]/gu)].length
            : 0;
        return {
            fields: [...file.content.matchAll(/\b(?:public|private|protected)\s+(?:[\w\\]+\s+)?\$\w+\s*(?:=|;)/gu)].length + fillableFields,
            methods: [...file.content.matchAll(/\bfunction\s+\w+\s*\(/gu)].length,
        };
    }
    if (file.extension === '.go') {
        const structMatch = /\bstruct\s*\{([\s\S]*?)\n\}/u.exec(file.content);
        const structBody = structMatch?.[1] ?? '';
        return {
            fields: [...structBody.matchAll(/^\s*[A-Z]\w*\s+[\w*.[\]]+/gmu)].length,
            methods: [...file.content.matchAll(/^\s*func\s+\([^)]*\)\s+\w+\s*\(/gmu)].length,
        };
    }
    return { fields: 0, methods: 0 };
}
function countLegacyTypeScriptOperationMatches(file) {
    file.legacyOperationCount ??= [...file.content.matchAll(/((?:\s*@\w+(?:\([^)]*\))?[^\n]*\n)*)\s*(?:public\s+|private\s+|protected\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{/gu)].length;
    return file.legacyOperationCount;
}
function countServiceOperations(file, serviceMethods) {
    return file.extension === '.java' ? serviceMethods.length : countLegacyTypeScriptOperationMatches(file);
}
function countTypeScriptConstructorParameters(file, astCache) {
    const sourceFile = parseSourceFile(file, astCache);
    let count = 0;
    const visit = (node) => {
        if (ts.isConstructorDeclaration(node)) {
            count += node.parameters.length;
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    return count;
}
function countJavaInjectedCollaborators(content) {
    return [...content.matchAll(/@(Autowired|Inject)\b|private\s+final\s+\w+(?:<[^>]+>)?\s+\w+\s*;/gu)].length;
}
function sortUnique(values) {
    return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
function isDbModelEvidenceToken(value) {
    const normalized = value.trim();
    if (normalized.length <= 1) {
        return false;
    }
    if (!/^[A-Za-z][A-Za-z0-9_]*$/u.test(normalized)) {
        return false;
    }
    const lower = normalized.toLowerCase();
    const excluded = new Set([
        'a',
        'all',
        'an',
        'doing',
        'first',
        'is',
        'outside',
        'resend',
        'scratch',
        'swagger',
        'the',
    ]);
    if (excluded.has(lower)) {
        return false;
    }
    return !/(?:client|provider|service|controller|guard|swagger|openapi)$/iu.test(normalized);
}
function isStructuredSqlTableEvidence(value, keyword, quote, following = '') {
    const normalized = value.trim();
    const hasStructuralEvidence = /^(?:from|update|into)$/iu.test(keyword) ||
        quote !== undefined && quote.length > 0 ||
        /[_.]/u.test(normalized) ||
        (/^join$/iu.test(keyword) && /\bon\b/iu.test(following));
    return hasStructuralEvidence && isDbModelEvidenceToken(normalized);
}
function extractSqlLikeLiterals(content) {
    return [...content.matchAll(/(['"`])([\s\S]{0,1200}?)\1/gu)]
        .map((match) => match[2] ?? '')
        .filter((literal) => /\b(?:select|update|delete|insert)\b[\s\S]{0,400}\b(?:from|join|into|update)\b/iu.test(literal));
}
function extractDbModelsTouched(content) {
    const models = new Set();
    for (const match of content.matchAll(/\b(?:this\.)?prisma\.([a-z]\w*)\./giu)) {
        if (match[1] && isDbModelEvidenceToken(match[1])) {
            models.add(match[1]);
        }
    }
    for (const sqlLiteral of extractSqlLikeLiterals(content)) {
        for (const match of sqlLiteral.matchAll(/\b(?:from|join|update|into)\s+(["`]?)([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)?)\1/giu)) {
            const keyword = /\b(from|join|update|into)\b/iu.exec(match[0])?.[1] ?? '';
            const following = sqlLiteral.slice((match.index ?? 0) + match[0].length, (match.index ?? 0) + match[0].length + 120);
            if (match[2] && isStructuredSqlTableEvidence(match[2], keyword, match[1], following)) {
                models.add(match[2]);
            }
        }
    }
    return sortUnique(models).slice(0, 6);
}
function extractExternalIntegrations(content) {
    const integrations = new Set();
    const patterns = [
        [/\b(?:HttpService|httpClient|RestTemplate|WebClient|axios|fetch)\b/iu, 'http'],
        [/\b(?:Mailer|mailClient|emailClient|sendEmail|send\w*Email)\b/iu, 'email'],
        [/\b(?:Kafka|Rabbit|Sqs|Queue|EventEmitter|ApplicationEventPublisher)\b/iu, 'messaging'],
        [/\b(?:Paddle|Stripe|Checkout|PortalSession)\b/iu, 'billing-provider'],
        [/\b(?:S3|putObject|upload)\b/iu, 'object-storage'],
    ];
    for (const [pattern, label] of patterns) {
        if (pattern.test(content)) {
            integrations.add(label);
        }
    }
    return sortUnique(integrations);
}
function countPersistenceCollaborators(content) {
    const collaborators = new Set();
    for (const match of content.matchAll(/\b(?:private\s+readonly\s+|private\s+|readonly\s+)?(\w*(?:Repository|Repo|Dao|Prisma|DataSource|EntityManager|JdbcTemplate))\b/gu)) {
        if (match[1]) {
            collaborators.add(match[1]);
        }
    }
    return collaborators.size;
}
function summarizeServiceEvidence(file, serviceMethods, repositoryCollaborators, astCache) {
    const methodCount = countServiceOperations(file, serviceMethods);
    const injectedCollaborators = file.extension === '.java'
        ? countJavaInjectedCollaborators(file.content)
        : countTypeScriptConstructorParameters(file, astCache);
    return {
        lines: file.content.split(/\r?\n/u).length,
        publicMethods: methodCount,
        injectedCollaborators,
        repositoryCollaborators: repositoryCollaborators.length,
        persistenceCollaborators: countPersistenceCollaborators(file.content),
        dbModelsTouched: extractDbModelsTouched(file.content),
        externalIntegrations: extractExternalIntegrations(file.content),
    };
}
function formatServiceEvidence(evidence) {
    const parts = [
        `loc=${evidence.lines}`,
        `methods=${evidence.publicMethods}`,
        `collaborators=${evidence.injectedCollaborators}`,
        `repositories=${evidence.repositoryCollaborators}`,
        `persistence=${evidence.persistenceCollaborators}`,
    ];
    if (evidence.dbModelsTouched.length > 0) {
        parts.push(`db=${evidence.dbModelsTouched.join('|')}`);
    }
    if (evidence.externalIntegrations.length > 0) {
        parts.push(`external=${evidence.externalIntegrations.join('|')}`);
    }
    return parts.join(', ');
}
function shouldSkipAnemicDomainModelFinding(file) {
    const baseName = path.basename(file.relativePath, path.extname(file.relativePath));
    return (/^BaseEntity$/u.test(baseName) ||
        /(?:View|Projection|Dto|Response|Translation|Record|Cache|Job|Schedule|Scheduler|Session|History|Log|Audit)$/u.test(baseName) ||
        /(^|\/)(?:dto|dtos|view|views|projection|projections|response|responses|records?|cache|job-queue|scheduler)\//iu.test(file.relativePath));
}
function getDomainEntityNames(file, astCache) {
    if (!['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(file.extension)) {
        const match = /\b(?:class|interface|type|struct)\s+([A-Z]\w*)\b/u.exec(file.content);
        return match?.[1] ? [match[1]] : [];
    }
    const sourceFile = parseSourceFile(file, astCache);
    const names = new Set();
    const visit = (node) => {
        if ((ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) && node.name) {
            names.add(node.name.text);
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    return [...names];
}
function hasStrongAggregateEvidence(file, entityNames) {
    const lowerPath = file.relativePath.toLowerCase();
    return (/(^|\/)(?:domain|aggregates?)\//u.test(lowerPath) ||
        entityNames.some((name) => /Aggregate$/u.test(name)) ||
        /\bAggregateRoot\b|@Aggregate\b/u.test(file.content));
}
function hasEntitySpecificServiceBusinessRules(entityNames, methods) {
    if (entityNames.length === 0) {
        return false;
    }
    const entityAliases = new Set(entityNames);
    for (const entityName of entityNames) {
        if (/(?:Entity|Model|Aggregate)$/u.test(entityName)) {
            const stem = entityName.replace(/(?:Entity|Model|Aggregate)$/u, '');
            if (stem.length >= 3) {
                entityAliases.add(stem);
                entityAliases.add(stem.charAt(0).toLowerCase() + stem.slice(1));
            }
        }
    }
    return methods.some((method) => {
        if (!hasRole(method.classification, 'service') || !businessRulePattern.test(method.body)) {
            return false;
        }
        return [...entityAliases].some((entityName) => new RegExp(`\\b${entityName}\\b`, 'u').test(method.file.content));
    });
}
function detectDomainFindings(files, classifications, methods, contract, astCache) {
    const findings = [];
    for (const file of files) {
        const classification = classifications.get(file.relativePath);
        if (!classification) {
            continue;
        }
        const lowerPath = file.relativePath.toLowerCase();
        if (hasRole(classification, 'entity') &&
            (0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(classification.scope) &&
            hasExplicitDomainModelEvidence(file) &&
            !/(dto|request|response|viewmodel|projection)/u.test(lowerPath) &&
            !shouldSkipAnemicDomainModelFinding(file)) {
            const entityNames = getDomainEntityNames(file, astCache);
            const counts = countDomainModelMembers(file, astCache);
            if (counts.fields >= 4 &&
                counts.methods <= 1 &&
                hasStrongAggregateEvidence(file, entityNames) &&
                hasEntitySpecificServiceBusinessRules(entityNames, methods)) {
                findings.push(createFinding({
                    id: RuleIds.DOM_ANEMIC_MODEL,
                    message: 'Domain/entity model is mostly state while business rule evidence lives in services.',
                    file,
                    line: 1,
                    module: classification.moduleName,
                    target: 'domain-model',
                }));
            }
        }
        if (hasRole(classification, 'entity') &&
            (0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(classification.scope) &&
            hasExplicitDomainModelEvidence(file) &&
            hasDomainFrameworkLeakEvidence(file) &&
            !/(dto|request|response|viewmodel|projection)/u.test(lowerPath)) {
            findings.push(createFinding({
                id: RuleIds.DOM_LOGIC_SCATTERED,
                message: 'Domain/entity model depends on web, controller, repository, or infrastructure framework concerns.',
                file,
                line: 1,
                module: classification.moduleName,
                target: 'domain-model',
            }));
        }
    }
    const oversizedServiceFindingsByFile = new Map();
    for (const method of methods) {
        const repositoryStems = extractRepositoryStems(method.body);
        const mutatedStems = extractMutatedRepositoryStems(method.body);
        const mutatedModules = new Set(mutatedStems.map((stem) => mapStemToModule(stem, contract)).filter((moduleName) => !!moduleName));
        if (hasRole(method.classification, 'service') &&
            method.classification.moduleName &&
            [...mutatedModules].some((moduleName) => moduleName !== method.classification.moduleName) &&
            !isCrossAggregateModificationRisk(method, contract)) {
            findings.push(createFinding({
                id: RuleIds.DOM_AGGREGATE_BOUNDARY_VIOLATION,
                message: `Service method '${method.name}' appears to reach into another aggregate/module repository directly.`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
        if (hasRole(method.classification, 'service')) {
            if (!(0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(method.classification.scope)) {
                continue;
            }
            const serviceMethods = methods.filter((candidate) => candidate.file.relativePath === method.file.relativePath);
            const serviceRepositoryStems = sortUnique(serviceMethods.flatMap((candidate) => extractRepositoryStems(candidate.body)));
            const publicOperationCount = countServiceOperations(method.file, serviceMethods);
            if ((serviceRepositoryStems.length >= 6 || publicOperationCount >= 18) &&
                !oversizedServiceFindingsByFile.has(method.file.relativePath)) {
                const evidence = summarizeServiceEvidence(method.file, serviceMethods, serviceRepositoryStems, astCache);
                oversizedServiceFindingsByFile.set(method.file.relativePath, createFinding({
                    id: RuleIds.DOM_OVERSIZED_BOUNDARY,
                    message: `Domain/application service has enough aggregate collaborators or operations to suggest an oversized ownership boundary (${formatServiceEvidence(evidence)}).`,
                    file: method.file,
                    line: 1,
                    module: method.classification.moduleName,
                    target: 'service',
                }));
            }
        }
        if ((0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(method.classification.scope) &&
            (hasRole(method.classification, 'controller') || hasRole(method.classification, 'repository')) &&
            businessRulePattern.test(method.body) &&
            /\bif\s*\(|\bswitch\s*\(|\bthrow\s+new\b/u.test(method.body)) {
            findings.push(createFinding({
                id: RuleIds.DOM_LOGIC_SCATTERED,
                message: `Business rule evidence appears in a ${hasRole(method.classification, 'controller') ? 'controller' : 'repository'} method '${method.name}'.`,
                file: method.file,
                line: method.line,
                module: method.classification.moduleName,
                target: method.name,
            }));
        }
    }
    findings.push(...oversizedServiceFindingsByFile.values());
    return findings;
}
async function detectTransactionDomainFindings(input) {
    void input.config;
    const disabled = input.disabledRuleIds ?? new Set();
    const scanStarted = Date.now();
    const files = await collectSourceFiles(input.workspaceRoot, input.sourceInventory);
    (0, validationProfiling_1.emitValidationProfile)('AP-TXN/AP-DOM analyzer file scan', Date.now() - scanStarted, [
        { name: 'files', value: files.length },
    ]);
    const classifications = await (0, validationProfiling_1.profileValidationPhase)('AP-TXN/AP-DOM classify source files', () => Promise.resolve(new Map(files.map((file) => [file.relativePath, classifySourceFile(file, input.contract, input.astCache)]))), (result) => [{ name: 'files', value: result.size }]);
    const methods = await (0, validationProfiling_1.profileValidationPhase)('AP-TXN/AP-DOM extract methods', () => Promise.resolve(files.flatMap((file) => {
        const classification = classifications.get(file.relativePath);
        if (!classification) {
            return [];
        }
        const fileStarted = Date.now();
        const extracted = extractMethods(file, classification, input.astCache);
        emitSlowTransactionDomainFileProfile('extract methods', file, Date.now() - fileStarted, [
            { name: 'methods', value: extracted.length },
            { name: 'bytes', value: file.content.length },
        ]);
        return extracted;
    })), (result) => [{ name: 'methods', value: result.length }]);
    const transactionFindings = await (0, validationProfiling_1.profileValidationPhase)('AP-TXN transaction findings', () => Promise.resolve(detectTransactionFindings(methods, input.contract)), (result) => [{ name: 'findings', value: result.length }]);
    const domainFindings = await (0, validationProfiling_1.profileValidationPhase)('AP-DOM domain findings', () => Promise.resolve(detectDomainFindings(files, classifications, methods, input.contract, input.astCache)), (result) => [{ name: 'findings', value: result.length }]);
    return [
        ...transactionFindings,
        ...domainFindings,
    ]
        .filter((finding) => !disabled.has(finding.id))
        .sort((left, right) => {
        const fileCompare = (left.sourceFile ?? '').localeCompare(right.sourceFile ?? '');
        if (fileCompare !== 0) {
            return fileCompare;
        }
        return left.id.localeCompare(right.id);
    });
}
//# sourceMappingURL=transactionDomainAnalysis.js.map