"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.detectAuthorizationTenantFindings = detectAuthorizationTenantFindings;
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 RuleIds = {
    AUTH_MISSING_ENFORCEMENT: 'AP-AUTH-001',
    AUTH_INCONSISTENT_ENFORCEMENT: 'AP-AUTH-002',
    AUTH_WRONG_LAYER: 'AP-AUTH-003',
    TEN_MISSING_CONTEXT_PROPAGATION: 'AP-TEN-001',
    TEN_MISSING_ENFORCEMENT: 'AP-TEN-002',
    TEN_CROSS_TENANT_RISK: 'AP-TEN-003',
    TEN_MODEL_UNDOCUMENTED: 'AP-TEN-004',
};
const maxFileSizeBytes = 384 * 1024;
const supportedExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.java', '.py', '.php', '.cs', '.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 protectedOperationPattern = /\b(create|update|delete|remove|revoke|approve|assign|invite|export|payment|billing|member|team|org|repository|token|policy|exception|admin)\b/iu;
const publicEndpointPattern = /\b(health|ready|live|login|logout|callback|oauth|public|static|status)\b/iu;
const authEvidencePattern = /@(?:UseGuards|Roles|Permissions|RequirePermission|PreAuthorize|PostAuthorize|Secured|RolesAllowed)\b|\[(?:Authorize)\b[^\]]*\]|\b(?:Depends|Security)\s*\([^)]*(?:auth|oauth|current_user|currentUser|permission|role|scope)|\b(?:auth|authn|authz|authorize|authorized|authorization|permission|permissions|role|roles|policy|can|jwt|bearer|oauth|current_user|currentUser|requireRole\w*|RequireRole\w*|require_role|AuthGuard|JwtAuthGuard|RolesGuard|PermissionGuard|SecurityContextHolder|Authentication|Principal)(?:Middleware|Guard|Policy|Checker|Dependency)?\b/iu;
const publicEvidencePattern = /@Public\b|@PermitAll\b|\[AllowAnonymous\b[^\]]*\]|permitAll\s*\(|anonymous\(\)|permitAll\b|AllowAnonymous\b/u;
const roleEvidencePattern = /@(?:Roles|Permissions|RequirePermission|PreAuthorize|Secured|RolesAllowed)\s*(?:\(|\b)([^)\n]*)|\[Authorize\s*\([^)\]]*(?:Roles|Policy)\s*=|(?:requireRole\w*|RequireRole\w*|require_role|RoleChecker|adminMiddleware|can:|permission:|scopes?\s*=|Security\s*\([^)]*scopes)\b/u;
const tenantSignalPattern = /\b(?:tenantId|tenant_id|organizationId|organization_id|orgId|org_id|workspaceId|workspace_id|accountId|account_id|companyId|company_id|customerId|customer_id|TenantContext|CurrentTenant|TenantResolver|TenantProvider|request\.tenant|req\.tenant|user\.tenantId|user\.tenant_id|user\.organizationId|user\.organization_id|user\.orgId|user\.accountId|user\.workspaceId|user\.companyId|user\.customerId|HibernateFilter|CurrentTenant)\b/u;
const tenantPredicateEvidencePattern = /\b(?:tenantId|tenant_id|organizationId|organization_id|orgId|org_id|workspaceId|workspace_id|accountId|account_id|companyId|company_id|customerId|customer_id|TenantContext|CurrentTenant|TenantResolver|TenantProvider|request\.tenant|req\.tenant|user\.tenantId|user\.tenant_id|user\.organizationId|user\.organization_id|user\.orgId|user\.accountId|user\.workspaceId|user\.companyId|user\.customerId|HibernateFilter|CurrentTenant)\b/u;
const repositoryRolePattern = /\b(?:role|roles|permission|permissions|isAdmin|hasRole|hasPermission|SecurityContextHolder|Authentication|Principal)\b/u;
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 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 createFinding(input) {
    return {
        id: input.id,
        severity: input.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 getTypeScriptDeclaredRoles(file, astCache) {
    const sourceFile = 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);
    const roles = new Set();
    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');
            }
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    return [...roles];
}
function getDeclaredRoles(file, astCache) {
    if (file.extension === '.ts' || file.extension === '.tsx' || file.extension === '.js' || file.extension === '.jsx' || file.extension === '.mjs' || file.extension === '.cjs') {
        return getTypeScriptDeclaredRoles(file, astCache);
    }
    if (file.extension === '.java' || file.extension === '.cs') {
        const roles = new Set();
        if (/\bclass\s+\w*Controller\b/u.test(file.content) || /@(?:Controller|RestController)\b|\[ApiController\]/u.test(file.content)) {
            roles.add('controller');
        }
        if (/\bclass\s+\w*Service\b/u.test(file.content) || /@Service\b/u.test(file.content)) {
            roles.add('service');
        }
        if (/\bclass\s+\w*(?:Repository|Dao)\b/u.test(file.content) || /@Repository\b/u.test(file.content)) {
            roles.add('repository');
        }
        return [...roles];
    }
    const lowerPath = file.relativePath.toLowerCase();
    const roles = new Set();
    if (/(^|\/)(controllers?|routers?|routes?|handlers?)\//u.test(lowerPath) || /controller\.(php|py|go)$/u.test(lowerPath)) {
        roles.add('controller');
    }
    if (/(^|\/)(services?)\//u.test(lowerPath) || /service\.(php|py|go)$/u.test(lowerPath)) {
        roles.add('service');
    }
    if (/(^|\/)(repositories?|repository|dao|data)\//u.test(lowerPath) || /(repository|repo|dao)\.(php|py|go)$/u.test(lowerPath)) {
        roles.add('repository');
    }
    return [...roles];
}
function isControllerFile(file, astCache) {
    const roles = getDeclaredRoles(file, astCache);
    return (/(^|[./_-])controller\.(ts|tsx|java)$/iu.test(file.relativePath) ||
        roles.includes('controller'));
}
function isRepositoryFile(file, astCache) {
    const roles = getDeclaredRoles(file, astCache);
    return (/(^|[./_-])(?:repository|repo|dao)\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/iu.test(file.relativePath) ||
        roles.includes('repository'));
}
function isServiceFile(file, astCache) {
    const roles = getDeclaredRoles(file, astCache);
    return (/(^|[./_-])service\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/iu.test(file.relativePath) ||
        roles.includes('service'));
}
function stringLiteralFromCall(value) {
    const match = /['"`]([^'"`]*)['"`]/u.exec(value);
    return match?.[1] ?? '';
}
function routeVerbFromName(value) {
    const lower = value.toLowerCase();
    if (lower.includes('delete') || lower.includes('destroy') || lower.includes('remove')) {
        return 'delete';
    }
    if (lower.includes('post') || lower.includes('store') || lower.includes('create')) {
        return 'post';
    }
    if (lower.includes('put') || lower.includes('patch') || lower.includes('update')) {
        return 'write';
    }
    return 'get';
}
function createEndpoint(input) {
    return {
        name: input.name,
        verb: input.verb,
        pathText: input.pathText,
        line: input.line,
        decorators: input.decorators ?? '',
        body: input.body ?? '',
        file: input.file,
        ...(input.moduleName ? { moduleName: input.moduleName } : {}),
    };
}
function findPythonFunctionBody(content, startIndex, searchFromIndex) {
    const nextDefinition = content.slice(searchFromIndex).search(/\n(?:async\s+def|def|class)\s+/u);
    if (nextDefinition < 0) {
        return content.slice(startIndex);
    }
    return content.slice(startIndex, searchFromIndex + nextDefinition);
}
function extractPythonEndpoints(file, moduleName) {
    const endpoints = [];
    const pattern = /((?:\s*@\w+\.(?:get|post|put|patch|delete)\s*\([^)]*\)\s*\n)+)\s*(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)\s*:/gu;
    for (const match of file.content.matchAll(pattern)) {
        const decorators = match[1] ?? '';
        const routeMatch = /@(\w+)\.(get|post|put|patch|delete)\s*\(([^)]*)\)/u.exec(decorators);
        if (!routeMatch?.[2]) {
            continue;
        }
        const body = findPythonFunctionBody(file.content, match.index ?? 0, (match.index ?? 0) + match[0].length);
        endpoints.push(createEndpoint({
            file,
            moduleName,
            name: match[2] ?? 'handler',
            verb: routeVerbFromName(routeMatch[2]),
            pathText: `${stringLiteralFromCall(routeMatch[3] ?? '')} ${match[3] ?? ''}`,
            line: lineNumberForOffset(file.content, match.index ?? 0),
            decorators,
            body,
        }));
    }
    return endpoints;
}
function extractPhpEndpoints(file, moduleName) {
    const endpoints = [];
    const routePattern = /\bRoute::(get|post|put|patch|delete)\s*\(\s*(['"])([^'"]+)\2\s*,\s*([^)]+)\)(?:->middleware\s*\(([^;]+)\))?/giu;
    for (const match of file.content.matchAll(routePattern)) {
        const handler = match[4] ?? '';
        const handlerName = /['"](\w+)['"]/.exec(handler)?.[1] ?? `${match[1] ?? 'route'}Route`;
        endpoints.push(createEndpoint({
            file,
            moduleName,
            name: handlerName,
            verb: routeVerbFromName(match[1] ?? 'get'),
            pathText: match[3] ?? '/',
            line: lineNumberForOffset(file.content, match.index ?? 0),
            decorators: match[5] ?? '',
            body: match[0],
        }));
    }
    return endpoints;
}
function extractCSharpEndpoints(file, moduleName) {
    const endpoints = [];
    const classPattern = /((?:\s*\[[^\]]+\]\s*)*)\s*public\s+class\s+(\w+Controller)\b[\s\S]*?\{/gu;
    for (const classMatch of file.content.matchAll(classPattern)) {
        const classAttributes = classMatch[1] ?? '';
        const classStart = classMatch.index ?? 0;
        const classBody = file.content.slice(classStart);
        const methodPattern = /((?:\s*\[(?:HttpGet|HttpPost|HttpPut|HttpPatch|HttpDelete|Authorize|AllowAnonymous)[^\]]*\]\s*)+)\s*public\s+(?:async\s+)?[\w<>., ?\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/gu;
        for (const match of classBody.matchAll(methodPattern)) {
            const attributes = `${classAttributes}\n${match[1] ?? ''}`;
            const methodMatch = /\[(HttpGet|HttpPost|HttpPut|HttpPatch|HttpDelete)\b/u.exec(attributes);
            if (!methodMatch?.[1]) {
                continue;
            }
            const absoluteIndex = classStart + (match.index ?? 0);
            endpoints.push(createEndpoint({
                file,
                moduleName,
                name: match[2] ?? 'handler',
                verb: routeVerbFromName(methodMatch[1]),
                pathText: attributes,
                line: lineNumberForOffset(file.content, absoluteIndex),
                decorators: attributes,
                body: file.content.slice(absoluteIndex, Math.min(file.content.length, absoluteIndex + 1200)),
            }));
        }
    }
    return endpoints;
}
function extractGoEndpoints(file, moduleName) {
    const endpoints = [];
    const routePattern = /\b\w+\.(GET|POST|PUT|PATCH|DELETE|Get|Post|Put|Patch|Delete|HandleFunc)\s*\(\s*["`]([^"`]+)["`]\s*,\s*([^)]+)\)/gu;
    for (const match of file.content.matchAll(routePattern)) {
        const handlerExpression = match[3] ?? '';
        endpoints.push(createEndpoint({
            file,
            moduleName,
            name: /(\w+)\s*(?:\)|$)/u.exec(handlerExpression)?.[1] ?? 'handler',
            verb: match[1] === 'HandleFunc' ? 'get' : routeVerbFromName(match[1] ?? 'get'),
            pathText: match[2] ?? '/',
            line: lineNumberForOffset(file.content, match.index ?? 0),
            decorators: handlerExpression,
            body: match[0],
        }));
    }
    return endpoints;
}
function extractExpressEndpoints(file, moduleName) {
    const endpoints = [];
    const routePattern = /\b(?:router|app|[A-Za-z_]\w*Router)\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*([^)]+)\)/giu;
    for (const match of file.content.matchAll(routePattern)) {
        const chain = match[3] ?? '';
        endpoints.push(createEndpoint({
            file,
            moduleName,
            name: /(\w+)\s*(?:\)|$)/u.exec(chain)?.[1] ?? `${match[1] ?? 'route'}Route`,
            verb: routeVerbFromName(match[1] ?? 'get'),
            pathText: match[2] ?? '/',
            line: lineNumberForOffset(file.content, match.index ?? 0),
            decorators: chain,
            body: match[0],
        }));
    }
    return endpoints;
}
function extractEndpoints(file, moduleName) {
    if (file.extension === '.py') {
        return extractPythonEndpoints(file, moduleName);
    }
    if (file.extension === '.php') {
        return extractPhpEndpoints(file, moduleName);
    }
    if (file.extension === '.cs') {
        return extractCSharpEndpoints(file, moduleName);
    }
    if (file.extension === '.go') {
        return extractGoEndpoints(file, moduleName);
    }
    if (file.extension === '.js' || file.extension === '.jsx' || file.extension === '.mjs' || file.extension === '.cjs') {
        return extractExpressEndpoints(file, moduleName);
    }
    const endpoints = [];
    const content = file.content;
    const tsEndpoint = /((?:\s*@(?:Get|Post|Put|Patch|Delete|Controller|UseGuards|Roles|Permissions|RequirePermission|Public)\b[^\n]*\n)+)\s*(?:public\s+|private\s+|protected\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/gu;
    const javaEndpoint = /((?:\s*@(?:GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping|PreAuthorize|PostAuthorize|Secured|RolesAllowed|PermitAll)\b[^\n]*\n)+)\s*(?:public|private|protected)\s+[\w<>, ?\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/gu;
    const pattern = file.extension === '.java' ? javaEndpoint : tsEndpoint;
    for (const match of content.matchAll(pattern)) {
        const decorators = match[1] ?? '';
        if (!/@(?:Get|Post|Put|Patch|Delete|GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping)\b/u.test(decorators)) {
            continue;
        }
        const start = match.index ?? 0;
        const bodyEnd = content.indexOf('\n}', start + match[0].length);
        endpoints.push({
            name: match[2] ?? 'handler',
            verb: /@(Delete|DeleteMapping)\b/u.test(decorators)
                ? 'delete'
                : /@(Post|PostMapping)\b/u.test(decorators)
                    ? 'post'
                    : /@(Put|Patch|PutMapping|PatchMapping)\b/u.test(decorators)
                        ? 'write'
                        : 'get',
            pathText: decorators,
            line: lineNumberForOffset(content, start),
            decorators,
            body: content.slice(start, bodyEnd > start ? bodyEnd : Math.min(content.length, start + 1200)),
            file,
            moduleName,
        });
    }
    if (file.extension === '.ts' || file.extension === '.tsx') {
        endpoints.push(...extractExpressEndpoints(file, moduleName));
    }
    return endpoints;
}
function isPublicEndpoint(endpoint) {
    return (publicEvidencePattern.test(endpoint.decorators) ||
        publicEndpointPattern.test(endpoint.name) ||
        publicEndpointPattern.test(endpoint.pathText));
}
function hasAuthEvidence(endpoint) {
    return authEvidencePattern.test(`${endpoint.decorators}\n${endpoint.body}\n${endpoint.pathText}`) || /\b(?:request|req)\.user\b|\buser\./u.test(endpoint.body);
}
function getRoleEvidence(endpoint) {
    const match = roleEvidencePattern.exec(`${endpoint.decorators}\n${endpoint.body}\n${endpoint.pathText}`);
    return match?.[0]?.trim();
}
function isProtectedLooking(endpoint) {
    return endpoint.verb !== 'get' || protectedOperationPattern.test(endpoint.name) || protectedOperationPattern.test(endpoint.pathText);
}
function detectAuthorizationFindings(files, contract, astCache) {
    const findings = [];
    for (const file of files) {
        const moduleName = inferModuleForPath(contract, file.relativePath);
        const endpoints = extractEndpoints(file, moduleName);
        if (endpoints.length === 0 && !isControllerFile(file, astCache)) {
            continue;
        }
        const protectedPeers = endpoints.filter((endpoint) => hasAuthEvidence(endpoint) && !isPublicEndpoint(endpoint));
        if (protectedPeers.length < 2) {
            continue;
        }
        for (const endpoint of endpoints) {
            if (isPublicEndpoint(endpoint) || hasAuthEvidence(endpoint) || !isProtectedLooking(endpoint)) {
                continue;
            }
            findings.push(createFinding({
                id: RuleIds.AUTH_MISSING_ENFORCEMENT,
                message: `Endpoint '${endpoint.name}' lacks authorization evidence while peer endpoint '${protectedPeers[0]?.name ?? 'unknown'}' is protected.`,
                file,
                line: endpoint.line,
                module: moduleName,
                target: endpoint.name,
            }));
            const hasRolePeer = protectedPeers.some((peer) => getRoleEvidence(peer));
            if (hasRolePeer) {
                findings.push(createFinding({
                    id: RuleIds.AUTH_INCONSISTENT_ENFORCEMENT,
                    message: `Endpoint '${endpoint.name}' has weaker authorization evidence than protected peer '${protectedPeers.find((peer) => getRoleEvidence(peer))?.name ?? 'unknown'}'.`,
                    file,
                    line: endpoint.line,
                    module: moduleName,
                    target: endpoint.name,
                }));
            }
        }
    }
    for (const file of files.filter((candidate) => isRepositoryFile(candidate, astCache))) {
        const roleMatch = repositoryRolePattern.exec(file.content);
        if (!roleMatch) {
            continue;
        }
        if (/\b(?:tenantId|tenant_id|organizationId|orgId|workspaceId)\b/u.test(file.content) && !/\b(?:role|permission|isAdmin|hasRole|hasPermission)\b/u.test(file.content)) {
            continue;
        }
        findings.push(createFinding({
            id: RuleIds.AUTH_WRONG_LAYER,
            message: 'Repository/data layer appears to contain role or permission authorization decisions.',
            file,
            line: lineNumberForOffset(file.content, roleMatch.index),
            module: inferModuleForPath(contract, file.relativePath),
            target: 'repository',
        }));
    }
    return findings;
}
function hasTenantEvidence(value) {
    return tenantSignalPattern.test(value);
}
function firstPatternWithoutTenant(value, patterns, windowSize = 360) {
    for (const pattern of patterns) {
        pattern.lastIndex = 0;
        for (const match of value.matchAll(pattern)) {
            const index = match.index ?? 0;
            const snippet = value.slice(index, index + windowSize);
            if (!tenantPredicateEvidencePattern.test(snippet)) {
                return match;
            }
        }
    }
    return undefined;
}
function containsRepositoryCallWithoutTenant(value) {
    return firstPatternWithoutTenant(value, [
        /\b(?:this\.)?\w*(?:Repository|Repo)\.\w+\s*\(/gu,
        /\b(?:this->)?\w*(?:Repository|Repo)->\w+\s*\(/gu,
    ]) !== undefined;
}
function containsOrmOperationWithoutTenant(value) {
    return firstPatternWithoutTenant(value, [
        /\b(?:findMany|findUnique|findFirst|update|delete|deleteMany|updateMany|createQueryBuilder|createQuery|query)\s*\(/giu,
        /\b(?:createNativeQuery|executeUpdate)\s*\(/giu,
        /\b(?:FirstOrDefault|SingleOrDefault|Find|FindAsync|Where|ExecuteDelete|ExecuteUpdate|Remove|Update)\s*\(/gu,
        /\b(?:session|db)\.(?:query|execute|get|delete)\s*\(/giu,
        /\bselect\s*\(\s*\w+/giu,
        /\b(?:DB::table|[A-Z]\w*::(?:query|where|find|findOrFail|update|delete))\s*\(/g,
        /->(?:where|first|find|delete|update|get)\s*\(/g,
        /\b(?:QueryContext|QueryRowContext|ExecContext|Query|Exec|Raw|Where|First|Find|Delete|Updates?)\s*\(/g,
    ]) !== undefined;
}
function isDestructiveTenantRisk(value) {
    const destructiveMatch = firstPatternWithoutTenant(value, [
        /\b(?:update|delete|deleteMany|updateMany|createQuery|createNativeQuery|executeUpdate|ExecuteDelete|ExecuteUpdate|Remove|Delete|ExecContext|Exec|query)\s*\(/giu,
        /\b(?:session|db)\.(?:execute|delete)\s*\(/giu,
        /->(?:delete|update)\s*\(/g,
        /\b[A-Z]\w*::(?:delete|update)\s*\(/g,
    ], 420);
    if (!destructiveMatch) {
        return false;
    }
    const snippet = value.slice(destructiveMatch.index ?? 0, (destructiveMatch.index ?? 0) + 420);
    return /\b(?:delete|update|remove|ExecuteDelete|ExecuteUpdate)\b/iu.test(snippet);
}
function detectTenantFindings(input) {
    if (input.config.tenantModel === 'single_tenant') {
        return [];
    }
    const findings = [];
    const hasAnyTenantEvidence = input.files.some((file) => hasTenantEvidence(file.content));
    if (!hasAnyTenantEvidence && input.config.tenantModel === 'same_db_same_schema') {
        const firstSource = input.files.find((file) => isRepositoryFile(file, input.astCache) || isServiceFile(file, input.astCache));
        if (firstSource) {
            findings.push(createFinding({
                id: RuleIds.TEN_MODEL_UNDOCUMENTED,
                severity: 'info',
                message: 'Architecture config uses same-schema tenancy, but scanned source has little tenant context evidence.',
                file: firstSource,
                line: 1,
                module: inferModuleForPath(input.contract, firstSource.relativePath),
            }));
        }
        return findings;
    }
    for (const file of input.files) {
        const moduleName = inferModuleForPath(input.contract, file.relativePath);
        if (isServiceFile(file, input.astCache) && hasTenantEvidence(file.content) && containsRepositoryCallWithoutTenant(file.content)) {
            findings.push(createFinding({
                id: RuleIds.TEN_MISSING_CONTEXT_PROPAGATION,
                message: 'Service appears to use tenant context but calls a repository without passing tenant/org scope.',
                file,
                line: lineNumberForOffset(file.content, file.content.search(/\b\w*(?:Repository|Repo)\.\w+\s*\(/u)),
                module: moduleName,
                target: 'service',
            }));
        }
        if (input.config.tenantModel === 'same_db_same_schema' && isRepositoryFile(file, input.astCache)) {
            if (containsOrmOperationWithoutTenant(file.content)) {
                findings.push(createFinding({
                    id: RuleIds.TEN_MISSING_ENFORCEMENT,
                    message: 'Tenant-scoped repository/query operation lacks tenant/org predicate evidence.',
                    file,
                    line: lineNumberForOffset(file.content, file.content.search(/\b(?:findMany|findUnique|findFirst|update|delete|deleteMany|updateMany|createQueryBuilder|createQuery|query)\s*\(/u)),
                    module: moduleName,
                    target: 'repository',
                }));
            }
            if (isDestructiveTenantRisk(file.content)) {
                findings.push(createFinding({
                    id: RuleIds.TEN_CROSS_TENANT_RISK,
                    message: 'Destructive tenant-scoped data operation appears to be scoped only by entity id.',
                    file,
                    line: lineNumberForOffset(file.content, file.content.search(/\b(?:update|delete|deleteMany|updateMany)\s*\(/u)),
                    module: moduleName,
                    target: 'repository',
                }));
            }
        }
    }
    return findings;
}
async function detectAuthorizationTenantFindings(input) {
    const disabled = input.disabledRuleIds ?? new Set();
    const started = Date.now();
    const files = await collectSourceFiles(input.workspaceRoot, input.sourceInventory);
    (0, validationProfiling_1.emitValidationProfile)('AP-AUTH/AP-TEN analyzer file scan', Date.now() - started, [
        { name: 'files', value: files.length },
    ]);
    return [
        ...detectAuthorizationFindings(files, input.contract, input.astCache),
        ...detectTenantFindings({ files, config: input.config, contract: input.contract, astCache: input.astCache }),
    ]
        .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=authorizationTenantAnalysis.js.map