import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; const scanRoots = ["apps", "packages"]; const ignoredScanSegments = new Set(["node_modules", "dist", ".vite", "coverage", ".next", "generated"]); const sourceLikeFilePattern = /\.(?:ts|tsx|js|jsx|mjs|cjs|json|sql|prisma)$/; const prismaMigrationPrefix = "apps/api/prisma/migrations/"; const generatedPrismaPrefix = "apps/api/src/generated/prisma/"; const stateTransitionModulePrefix = "apps/api/src/modules/state-transition/"; const auditModulePrefix = "apps/api/src/modules/audit/"; const jobsModulePrefix = "apps/api/src/modules/jobs/"; const jobExecutionStorePath = "apps/api/src/modules/jobs/index.ts"; const initialAuditLogAppendOnlyFunctionMigration = "apps/api/prisma/migrations/20260601040253_s1_app_foundation/migration.sql"; const historicalPrismaSchemaTest = "apps/api/src/prisma.schema.spec.ts"; const auditTriggerAssertionFiles = new Set([historicalPrismaSchemaTest, "apps/api/src/modules/audit/audit.spec.ts"]); const stateTransitionImportFixtureTests = new Set([ "apps/api/src/modules/jobs/jobs.spec.ts", "apps/api/src/modules/jobs/jobs.api.spec.ts", "apps/api/src/modules/projects/api-runtime.spec.ts", "apps/api/src/modules/projects/projects.api.spec.ts" ]); const laterPhaseDeniedTerms = [ "creation-agent/messages", "compile-game-ir", "compile-game-config", "validate-game-logic", "web-runtime", "publish-review-feed", "channel-readiness", "deploy-readiness", "GameConfig", "GameLogicModule", "RuntimeSdkContract", "SimulationRuleSet", "NpcBehaviorGraph", "game-config", "game-logic", "mini-game-conversion", "GamePackage", "PromptRecord", "AIGameDesignDraft", "DesignSectionPatch", "DesignSection", "GameIRArtifact", "GameIR", "ConversionReport", "ValidationReport", "MiniGameProject", "MiniGameCodeConversion", "FeedItem", "GameDailyStats", "CreatorDailyStats", "/events/batch" ]; const supportedPhases = new Set(["S1", "S2", "S3"]); const s2ApprovedTerms = new Set([ "creation-agent/messages", "compile-game-ir", "PromptRecord", "AIGameDesignDraft", "DesignSectionPatch", "DesignSection", "GameIRArtifact", "GameIR" ]); const s3ApprovedTerms = new Set([ "compile-game-config", "validate-game-logic", "GameConfig", "GameLogicModule", "RuntimeSdkContract", "SimulationRuleSet", "NpcBehaviorGraph", "game-config", "game-logic", "simulation", "ValidationReport" ]); const s3InheritedS2Terms = new Set(["GameIRArtifact", "GameIR"]); const s2LegacyS3TermAllowExactPaths = new Map([ [ "game-config", new Set([ "apps/api/prisma/migrations/20260604102042_s2_task2_design_models/migration.sql", "apps/api/src/modules/creation-agent/creation-agent-ai-design-workbench.api.spec.ts", "apps/api/src/modules/creation-agent/index.ts", "apps/api/src/prisma.schema.spec.ts" ]) ], [ "game-logic", new Set(["apps/api/src/modules/harness-gate/harness-gate.spec.ts", "packages/harness-client/src/index.spec.ts"]) ], [ "NpcBehaviorGraph", new Set([ "apps/api/src/modules/game-ir/index.ts", "apps/web/src/components/workbench/MainCreationAgentChat.tsx", "packages/shared-contracts/src/index.ts", "packages/shared-contracts/src/index.spec.ts", "packages/shared-contracts/src/game-ir.ts", "packages/shared-contracts/src/game-ir.spec.ts" ]) ] ]); const s2ApprovedTermDenyPathSegments = new Set([ "simulation", "web-runtime", "minigame-conversion", "mini_game_conversion", "publish-review-feed", "publish_review_feed", "mini-game-conversion", "conversion", "feed", "runtime", "publish", "review" ]); const s2ApprovedTermDenyPathFragments = ["/events/batch/"]; const s2ApprovedTermAllowPathPrefixes = [ "apps/api/src/modules/creation-agent/", "apps/api/src/modules/ai-design/", "apps/api/src/modules/workbench/", "apps/api/src/modules/game-ir/", "apps/api/src/modules/compile-game-ir/", "apps/api/prisma/migrations/", "apps/web/src/app/api/workbench/", "apps/web/src/app/workbench/", "apps/web/src/components/workbench/" ]; const s2ApprovedTermAllowExactPaths = new Set([ "apps/api/prisma/schema.prisma", // Prisma schema contract tests需要直接断言 S2 设计模型及其 lineage 约束; // 仍保持精确路径放行,避免普通 API 测试借用 S2 术语绕过阶段门禁。 "apps/api/src/prisma.schema.spec.ts", "packages/shared-contracts/src/index.ts", "packages/shared-contracts/src/index.spec.ts", "packages/shared-contracts/src/workflow.ts", "packages/shared-contracts/src/workflow.spec.ts", "packages/shared-contracts/src/ai-design.ts", "packages/shared-contracts/src/ai-design.spec.ts", "packages/shared-contracts/src/game-ir.ts", "packages/shared-contracts/src/game-ir.spec.ts" ]); const s3ApprovedTermAllowPathPrefixes = [ "apps/api/src/modules/game-config/", "apps/api/src/modules/game-logic/", "apps/api/src/modules/simulation/" ]; const s3ApprovedTermAllowExactPaths = new Set([ "packages/shared-contracts/src/index.ts", "packages/shared-contracts/src/index.spec.ts", "packages/shared-contracts/src/game-config.ts", "packages/shared-contracts/src/game-config.spec.ts", "packages/shared-contracts/src/game-logic-module.ts", "packages/shared-contracts/src/game-logic-module.spec.ts", "packages/shared-contracts/src/runtime-sdk-contract.ts", "packages/shared-contracts/src/runtime-sdk-contract.spec.ts" ]); const s3RuntimeSdkLifecycleDtoAllowExactPaths = new Set([ "packages/shared-contracts/src/runtime-sdk-contract.ts", "packages/shared-contracts/src/runtime-sdk-contract.spec.ts" ]); const s3OwnedPathPrefixes = [ "apps/api/src/modules/game-config/", "apps/api/src/modules/game-logic/", "apps/api/src/modules/simulation/" ]; const harnessValidationContractTerms = new Set([ "AIGameDesignDraft", "GameIR", "ConversionReport", "ValidationReport", "MiniGameProject", "MiniGameCodeConversion" ]); const harnessClientContractWatchTerms = new Set(harnessValidationContractTerms); const jobRuntimeFields = [ "status", "attempts", "nextRetryAt", "errorCode", "leaseToken", "leasedBy", "leaseExpiresAt", "lockVersion", "timeoutAt" ]; const rawSqlVariableResolutionMaxDepth = 6; const dynamicSqlIdentifierMarker = "__HUJING_DYNAMIC_SQL_IDENTIFIER__"; const auditLogAppendOnlyFunctionName = "app_reject_audit_log_mutation"; const auditPrismaMutationMethods = ["update", "updateMany", "updateManyAndReturn", "upsert", "delete", "deleteMany"]; const guardedPrismaWriteMethods = ["create", "update", "upsert", "createMany", "updateMany", "delete", "deleteMany"]; const guardedPrismaWriteModels = ["gameVersion", "reviewRecord", "lifecycleEvent"]; const guardedStateSqlTables = ["GameVersion", "ReviewRecord", "LifecycleEvent"]; const jobPrismaWriteMethods = ["create", "createMany", "createManyAndReturn", "update", "updateMany", "updateManyAndReturn", "upsert"]; const jobPrismaDeleteMethods = ["delete", "deleteMany"]; const s1AnchorTerms = ["MainCreationAgentSession", "AgentTask", "ReviewRecord", "LifecycleEvent"]; const s2ApprovedS1AnchorTerms = new Set(["MainCreationAgentSession", "AgentTask"]); const allowedStateTransitionImportPaths = new Set(["apps/api/src/modules/projects/index.ts"]); const allowedStateTransitionImportPrefixes = [stateTransitionModulePrefix]; function normalizePath(filePath) { return filePath.split(path.sep).join("/"); } function isIgnoredScanPath(filePath) { return filePath.split("/").some((segment) => ignoredScanSegments.has(segment)); } function isHarnessClientTestPath(filePath) { return /^packages\/harness-client\/.*\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(filePath); } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function sqlBareIdentifierPattern() { return String.raw`[A-Za-z_][\w$]*`; } function sqlTriggerIdentifierPattern() { // SQL 扫描会去掉双引号,quoted trigger name 里的连字符需作为同一 token fail-closed。 return String.raw`[^\s(),;]+`; } function validationContractReferenceSpans(content, term) { const spans = []; const pattern = new RegExp(`\\bvalidateContract\\s*\\(\\s*(["'])${escapeRegExp(term)}\\1`, "g"); for (const match of content.matchAll(pattern)) { const termOffset = match[0].lastIndexOf(term); if (typeof match.index === "number" && termOffset >= 0) { spans.push({ start: match.index + termOffset, end: match.index + termOffset + term.length }); } } return spans; } function enclosingBraceSpan(content, index) { const structure = maskCommentsOutsideStringLiterals(content); let smallestSpan = null; for (let cursor = 0; cursor < structure.length; cursor += 1) { if (structure[cursor] !== "{") continue; const closeBraceIndex = findMatchingBrace(structure, cursor); if (closeBraceIndex === -1 || index < cursor || index > closeBraceIndex) continue; const span = { start: cursor, end: closeBraceIndex + 1 }; if (!smallestSpan || span.end - span.start < smallestSpan.end - smallestSpan.start) { smallestSpan = span; } } return smallestSpan; } function hasHarnessCliContractArgumentContext(content, assertionIndex) { const span = enclosingBraceSpan(content, assertionIndex); const contextStart = Math.max(span?.start ?? 0, assertionIndex - 400); const contextEnd = Math.min(span?.end ?? content.length, assertionIndex + 220); const context = content.slice(contextStart, contextEnd); // args[2] 只有在同一 runner 小窗口内已证明 args[0..1] 指向真实 harness CLI contract 调用时才可作为合同名断言。 return /\bexpect\s*\(\s*args\s*\.\s*slice\s*\(\s*0\s*,\s*2\s*\)\s*\)\s*\.\s*to(?:Strict)?Equal\s*\(\s*\[\s*(?:realHarnessCli|[^\]]*validate-harness\.mjs[^\]]*)\s*,\s*(["'])--contract\1\s*\]\s*\)/s.test( context ); } function harnessCliContractArgumentAssertionSpans(content, term) { const spans = []; const pattern = new RegExp( `\\bexpect\\s*\\(\\s*args\\s*\\[\\s*2\\s*\\]\\s*\\)\\s*\\.\\s*toBe\\s*\\(\\s*(["'])${escapeRegExp(term)}\\1\\s*\\)`, "g" ); for (const match of content.matchAll(pattern)) { const termOffset = match[0].lastIndexOf(term); if (typeof match.index === "number" && termOffset >= 0 && hasHarnessCliContractArgumentContext(content, match.index)) { spans.push({ start: match.index + termOffset, end: match.index + termOffset + term.length }); } } return spans; } function isInSpan(index, spans) { return spans.some((span) => index >= span.start && index < span.end); } function isHarnessGateValidationTestPath(filePath) { return /^apps\/api\/src\/modules\/harness-gate\/.*\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(filePath); } function isAllowedHarnessValidationContractReferenceAt(filePath, content, term, index) { if (!harnessValidationContractTerms.has(term)) return false; if (!isHarnessClientTestPath(filePath) && !isHarnessGateValidationTestPath(filePath)) return false; if (isInSpan(index, validationContractReferenceSpans(content, term))) return true; // harness-client 的 runner 断言 `args[2]` 是 `--contract` 的合同名;这是 CLI validation 证据。 return isHarnessClientTestPath(filePath) && isInSpan(index, harnessCliContractArgumentAssertionSpans(content, term)); } function isAllowedHarnessClientContractReferenceAt(filePath, content, term, index) { if (!isHarnessClientTestPath(filePath)) return false; return isAllowedHarnessValidationContractReferenceAt(filePath, content, term, index); } function normalizeDeniedPathTerm(term) { return term.split(path.sep).join("/").replace(/^\/+|\/+$/g, ""); } function kebabCaseContractTerm(term) { return term .replace(/([a-z0-9])([A-Z])/g, "$1-$2") .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2") .toLowerCase(); } function pathContainsLaterPhaseTerm(filePath, term) { const deniedPathTerm = normalizeDeniedPathTerm(term); if (!deniedPathTerm) return false; const normalizedFilePath = normalizePath(filePath).replace(/^\/+|\/+$/g, ""); const pathWithBoundaries = `/${normalizedFilePath}/`; // route/module fragment 要按路径边界匹配,同时覆盖同名单文件模块。 if (deniedPathTerm.includes("/")) { const deniedSourceFilePattern = new RegExp(`(?:^|/)${escapeRegExp(deniedPathTerm)}${sourceLikeFilePattern.source}`); return pathWithBoundaries.includes(`/${deniedPathTerm}/`) || deniedSourceFilePattern.test(normalizedFilePath); } // symbol-like term 在路径中按 segment/filename 命中,覆盖 AIGameDesignDraft.ts 这类文件名。 const pathTermVariants = new Set([deniedPathTerm, kebabCaseContractTerm(deniedPathTerm)]); return normalizedFilePath .split("/") .some((segment) => [...pathTermVariants].some((pathTermVariant) => segment.includes(pathTermVariant))); } function contentDeniedTermVariants(term) { if (!term.startsWith("/")) return [term]; // 内容扫描把同一路由片段的绝对/相对写法视为等价,避免 "/events/batch" 漏掉 "events/batch"。 const normalizedTerm = term.replace(/^\/+/, ""); return normalizedTerm ? [term, normalizedTerm] : [term]; } function isTestFile(filePath) { return /\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(filePath); } function isHistoricalTestFile(filePath) { return filePath === historicalPrismaSchemaTest; } function isGeneratedPrismaFile(filePath) { return filePath.startsWith(generatedPrismaPrefix); } function isPrismaMigrationSqlPath(filePath) { return filePath.startsWith(prismaMigrationPrefix) && filePath.endsWith("/migration.sql"); } function auditLogAppendOnlyFunctionCreatePattern() { const auditLogAppendOnlyFunction = sqlFunctionReferencePattern(auditLogAppendOnlyFunctionName); return new RegExp(String.raw`\bCREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+${auditLogAppendOnlyFunction}\b`, "gi"); } function auditLogAppendOnlyFunctionInstallPattern() { const auditLogAppendOnlyFunction = sqlFunctionReferencePattern(auditLogAppendOnlyFunctionName); const dollarQuote = String.raw`(?:\$\$|\$[A-Za-z_][\w$]*\$)`; return new RegExp( String.raw`\bCREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+${auditLogAppendOnlyFunction}\s+RETURNS\s+trigger\s+AS\s+(${dollarQuote})([\s\S]*?)\2\s+LANGUAGE\s+plpgsql\b`, "gi" ); } function auditLogAppendOnlyFunctionCreateStatements(sqlSource) { const sql = stripSqlComments(sqlSource); return [...sql.matchAll(auditLogAppendOnlyFunctionCreatePattern())]; } function auditLogAppendOnlyFunctionDefinitions(sqlSource) { const sql = stripSqlComments(sqlSource); return [...sql.matchAll(auditLogAppendOnlyFunctionInstallPattern())].map((match) => ({ isCreateOrReplace: Boolean(match[1]), body: match[3] })); } function isCanonicalAuditLogAppendOnlyFunctionBody(functionBody) { // 允许 SQL 关键字大小写与空白变化,但函数体只能安装固定 append-only guard,不能夹带 RETURN/IF/EXECUTE 等语句。 return /^\s*BEGIN\s+RAISE\s+EXCEPTION\s+'AuditLog is append-only'\s+USING\s+ERRCODE\s*=\s*'42501'\s*;\s*END\s*;?\s*$/i.test( functionBody ); } function hasAllowedAuditLogAppendOnlyFunctionInstall(sqlSource, options = {}) { const createStatements = auditLogAppendOnlyFunctionCreateStatements(sqlSource); if (createStatements.length !== 1) return false; const definitions = auditLogAppendOnlyFunctionDefinitions(sqlSource); if (definitions.length !== 1) return false; const [definition] = definitions; if (definition.isCreateOrReplace && !options.allowOrReplace) return false; return isCanonicalAuditLogAppendOnlyFunctionBody(definition.body); } function auditLogAppendOnlyFunctionCreateOptions(filePath, content) { return { // 后续 migration 只允许 plain CREATE FUNCTION 安装固定 append-only guard,不允许替换已有函数。 allowAuditLogAppendOnlyFunctionPlainCreate: isPrismaMigrationSqlPath(filePath) && hasAllowedAuditLogAppendOnlyFunctionInstall(content), // 真实 S1 初始 migration 历史上使用 CREATE OR REPLACE;仅该文件按同一函数体语义兼容放行。 allowAuditLogAppendOnlyFunctionCreateOrReplace: filePath === initialAuditLogAppendOnlyFunctionMigration && hasAllowedAuditLogAppendOnlyFunctionInstall(content, { allowOrReplace: true }) }; } function isAllowedAuditTriggerAssertion(filePath, content, structure, mutationIndex) { if (!auditTriggerAssertionFiles.has(filePath)) return false; let searchCursor = mutationIndex; while (searchCursor >= 0) { const assertionIndex = structure.lastIndexOf("expectPrismaRejectedInSavepoint", searchCursor); if (assertionIndex === -1) return false; const openParenIndex = structure.indexOf("(", assertionIndex); if (openParenIndex === -1 || openParenIndex > mutationIndex) { searchCursor = assertionIndex - 1; continue; } const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1 || closeParenIndex < mutationIndex) { searchCursor = assertionIndex - 1; continue; } const assertionSource = content.slice(openParenIndex, closeParenIndex + 1); return /AuditLog is append-only/.test(assertionSource); } return false; } function hasForbiddenAuditMutation(filePath, content) { const structure = stripStringLiteralsExceptPropertyKeys(maskCommentsOutsideStringLiterals(content)); for (const match of prismaModelMethodCallMatchesInStructure(content, structure, ["auditLog"], auditPrismaMutationMethods)) { if (typeof match.index === "number" && isAllowedAuditTriggerAssertion(filePath, content, structure, match.index)) continue; return true; } return hasForbiddenRawAuditLogMutation(filePath, content, structure); } function guardedStateSqlTableReferencePattern() { return String.raw`(?:${guardedStateSqlTables.map(sqlTableReferencePattern).join("|")})`; } function hasRawGuardedStateWrite(sqlSource) { const sql = normalizeRawSqlScanSource(sqlSource); const guardedTable = guardedStateSqlTableReferencePattern(); const identifier = sqlBareIdentifierPattern(); // guarded state 的 raw SQL 必须跟 Prisma raw invocation 同源扫描,覆盖转义字符串、SQL 注释和 Postgres U& identifier。 return ( new RegExp(String.raw`\bUPDATE\s+(?:ONLY\s+)?${guardedTable}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\s+SET\b`, "i").test( sql ) || new RegExp(String.raw`\bINSERT\s+INTO\s+(?:ONLY\s+)?${guardedTable}\b`, "i").test(sql) || new RegExp(String.raw`\bDELETE\s+FROM\s+(?:ONLY\s+)?${guardedTable}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\b`, "i").test( sql ) || new RegExp(String.raw`\bMERGE\s+INTO\s+(?:ONLY\s+)?${guardedTable}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\b`, "i").test( sql ) || new RegExp(sqlTruncateTableListPattern(guardedTable), "i").test(sql) ); } function hasGuardedPrismaWrite(filePath, content) { const hasPrismaWrite = prismaModelMethodCallMatches(content, guardedPrismaWriteModels, guardedPrismaWriteMethods).length > 0; const hasRawSqlWrite = filePath.endsWith(".sql") ? hasRawGuardedStateWrite(content) : hasRawSqlInvocationMatching(content, hasRawGuardedStateWrite); return hasPrismaWrite || hasRawSqlWrite; } function hasRuntimeGuardSetter(content) { return /app\.state_transition_guard|set_config\s*\(|SET\s+LOCAL/i.test(content); } function isHexDigits(value) { return /^[0-9a-f]+$/i.test(value); } function decodeJavaScriptStringLiteralBody(body) { let output = ""; for (let index = 0; index < body.length; index += 1) { const char = body[index]; if (char !== "\\") { output += char; continue; } const next = body[index + 1]; if (typeof next === "undefined") { output += char; continue; } if (next === "\r" && body[index + 2] === "\n") { index += 2; continue; } if (next === "\n" || next === "\r") { index += 1; continue; } if (next === "x") { const hexValue = body.slice(index + 2, index + 4); if (hexValue.length === 2 && isHexDigits(hexValue)) { output += String.fromCharCode(Number.parseInt(hexValue, 16)); index += 3; continue; } } if (next === "u") { if (body[index + 2] === "{") { const closeBraceIndex = body.indexOf("}", index + 3); const hexValue = closeBraceIndex === -1 ? "" : body.slice(index + 3, closeBraceIndex); const codePoint = hexValue.length > 0 && isHexDigits(hexValue) ? Number.parseInt(hexValue, 16) : Number.NaN; if (codePoint >= 0 && codePoint <= 0x10ffff) { output += String.fromCodePoint(codePoint); index = closeBraceIndex; continue; } } const hexValue = body.slice(index + 2, index + 6); if (hexValue.length === 4 && isHexDigits(hexValue)) { output += String.fromCharCode(Number.parseInt(hexValue, 16)); index += 5; continue; } } const escapedCharacters = { "\\": "\\", '"': '"', "'": "'", "`": "`", n: "\n", r: "\r", t: "\t", b: "\b", f: "\f", v: "\v", 0: "\0" }; output += Object.hasOwn(escapedCharacters, next) ? escapedCharacters[next] : next; index += 1; } return output; } function decodeStaticJavaScriptStringLiteralSource(source) { const literalStart = skipWhitespaceForward(source, 0); const literalEndBoundary = skipWhitespaceBackward(source, source.length - 1); if (literalStart > literalEndBoundary) return null; const quote = source[literalStart]; if (quote !== '"' && quote !== "'" && quote !== "`") return null; const literalEnd = findStringLiteralEnd(source, literalStart); if (literalEnd !== literalEndBoundary) return null; const body = source.slice(literalStart + 1, literalEnd); // 带表达式的 template literal 继续走原有动态 SQL 归一化路径,避免把参数表达式误解成 SQL 文本。 if (quote === "`" && /\$\{/.test(body)) return null; return decodeJavaScriptStringLiteralBody(body); } function decodeStaticJavaScriptStringLiteralsInSource(source) { let output = ""; let index = 0; while (index < source.length) { const quote = source[index]; if ( quote === '"' && source[index - 1] === "&" && (source[index - 2] === "U" || source[index - 2] === "u") ) { let cursor = index + 1; while (cursor < source.length) { if (source[cursor] === '"' && source[cursor + 1] === '"') { cursor += 2; continue; } cursor += 1; if (source[cursor - 1] === '"') break; } output += source.slice(index, cursor); index = cursor; continue; } if (quote !== '"' && quote !== "'" && quote !== "`") { output += quote; index += 1; continue; } const literalEnd = findStringLiteralEnd(source, index); if (literalEnd === -1) { output += source.slice(index); break; } const body = source.slice(index + 1, literalEnd); // 动态 template literal 的表达式仍交给 dynamic SQL marker,不能在这里静态展开。 output += quote === "`" && /\$\{/.test(body) ? source.slice(index, literalEnd + 1) : `${quote}${decodeJavaScriptStringLiteralBody(body)}${quote}`; index = literalEnd + 1; } return output; } function sqlDollarQuoteTagAt(content, index) { return /^(?:\$\$|\$[A-Za-z_][\w$]*\$)/.exec(content.slice(index))?.[0] ?? null; } function decodePostgresUnicodeQuotedIdentifierBody(body, escapeChar = "\\") { let output = ""; for (let index = 0; index < body.length; index += 1) { const char = body[index]; if (char !== escapeChar) { output += char; continue; } if (body[index + 1] === "+") { const hexValue = body.slice(index + 2, index + 8); const codePoint = hexValue.length === 6 && isHexDigits(hexValue) ? Number.parseInt(hexValue, 16) : Number.NaN; if (codePoint >= 0 && codePoint <= 0x10ffff) { output += String.fromCodePoint(codePoint); index += 7; continue; } } const hexValue = body.slice(index + 1, index + 5); if (hexValue.length === 4 && isHexDigits(hexValue)) { output += String.fromCharCode(Number.parseInt(hexValue, 16)); index += 4; continue; } output += char; } return output; } function postgresUnicodeQuotedIdentifierEscapeClauseAt(content, index) { const keywordStart = skipWhitespaceForward(content, index); const keywordMatch = /^UESCAPE\b/i.exec(content.slice(keywordStart)); if (!keywordMatch) return null; const quoteStart = skipWhitespaceForward(content, keywordStart + keywordMatch[0].length); if (content[quoteStart] !== "'") return null; const escapeChar = content[quoteStart + 1]; if (typeof escapeChar === "undefined" || content[quoteStart + 2] !== "'") return null; return { escapeChar, end: quoteStart + 3 }; } function normalizePostgresUnicodeQuotedIdentifiers(content) { let output = ""; let index = 0; while (index < content.length) { if ((content[index] !== "U" && content[index] !== "u") || content[index + 1] !== "&" || content[index + 2] !== '"') { output += content[index]; index += 1; continue; } let body = ""; let cursor = index + 3; let closeQuoteIndex = -1; while (cursor < content.length) { const char = content[cursor]; const next = content[cursor + 1]; if (char === '"' && next === '"') { body += '""'; cursor += 2; continue; } if (char === '"') { closeQuoteIndex = cursor; break; } body += char; cursor += 1; } if (closeQuoteIndex === -1) { output += content[index]; index += 1; continue; } const escapeClause = postgresUnicodeQuotedIdentifierEscapeClauseAt(content, closeQuoteIndex + 1); // UESCAPE 紧跟 U& 标识符时会改变转义字符;归一化后必须移除该子句,避免污染表名模式。 output += `"${decodePostgresUnicodeQuotedIdentifierBody(body, escapeClause?.escapeChar ?? "\\")}"`; index = escapeClause?.end ?? closeQuoteIndex + 1; } return output; } function stripSqlComments(content) { let output = ""; let index = 0; while (index < content.length) { const char = content[index]; const next = content[index + 1]; if (char === "-" && next === "-") { // 真实行注释替换为空白但保留换行,避免擦掉下一条 SQL 语句的行边界。 while (index < content.length && content[index] !== "\n" && content[index] !== "\r") { output += " "; index += 1; } continue; } if (char === "/" && next === "*") { // 注释可能夹在关键字之间,必须保留 token separator,避免 DELETE/*x*/FROM 被拼成 DELETEFROM。 let depth = 1; output += " "; index += 2; while (index < content.length && depth > 0) { const blockChar = content[index]; const blockNext = content[index + 1]; if (blockChar === "/" && blockNext === "*") { output += " "; index += 2; depth += 1; continue; } if (blockChar === "*" && blockNext === "/") { output += " "; index += 2; depth -= 1; continue; } output += blockChar === "\n" || blockChar === "\r" ? blockChar : " "; index += 1; } continue; } if (char === "'") { output += char; index += 1; while (index < content.length) { const stringChar = content[index]; const stringNext = content[index + 1]; output += stringChar; if (stringChar === "\\") { if (typeof stringNext !== "undefined") output += stringNext; index += typeof stringNext === "undefined" ? 1 : 2; continue; } if (stringChar === "'" && stringNext === "'") { output += stringNext; index += 2; continue; } index += 1; if (stringChar === "'") break; } continue; } if (char === '"') { output += char; index += 1; while (index < content.length) { const identifierChar = content[index]; const identifierNext = content[index + 1]; output += identifierChar; if (identifierChar === "\\") { if (typeof identifierNext !== "undefined") output += identifierNext; index += typeof identifierNext === "undefined" ? 1 : 2; continue; } if (identifierChar === '"' && identifierNext === '"') { output += identifierNext; index += 2; continue; } index += 1; if (identifierChar === '"') break; } continue; } const dollarQuoteTag = char === "$" ? sqlDollarQuoteTagAt(content, index) : null; if (dollarQuoteTag) { const dollarQuoteEnd = content.indexOf(dollarQuoteTag, index + dollarQuoteTag.length); if (dollarQuoteEnd === -1) { output += content.slice(index); break; } output += content.slice(index, dollarQuoteEnd + dollarQuoteTag.length); index = dollarQuoteEnd + dollarQuoteTag.length; continue; } output += char; index += 1; } return output; } function normalizeRawSqlScanSource(content) { const sqlSource = decodeStaticJavaScriptStringLiteralSource(content) ?? decodeStaticJavaScriptStringLiteralsInSource(content); return normalizePostgresUnicodeQuotedIdentifiers(stripSqlComments(sqlSource)) .replace(/\\(["'`])/g, "$1") .replace(/\\n/g, "\n") .replace(/\\r/g, "\r") .replace(/\\t/g, "\t") .replace( /\$\{\s*Prisma\s*\.\s*raw\s*\(\s*((?:"(?:\\.|[^"\\])*")|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)\s*\)\s*\}/gs, (_match, literalSource) => { // Prisma.raw(...) 的静态 identifier 片段也必须按 JS 运行时字符串解码后再进入 SQL 归一化。 return ` ${decodeStaticJavaScriptStringLiteralSource(literalSource) ?? literalSource} `; } ) .replace(/\$\{[\s\S]*?\}/g, " ? ") .replace(/["'`]/g, " ") .replace(/\s*\+\s*/g, " ") .replace(/\s+/g, " "); } function sqlTableReferencePattern(tableName) { const identifier = sqlBareIdentifierPattern(); return String.raw`(?:${identifier}\s*\.\s*)?${escapeRegExp(tableName)}`; } function sqlAnyTableReferencePattern() { const identifier = sqlBareIdentifierPattern(); return String.raw`(?:${identifier}\s*\.\s*)?${identifier}`; } function sqlFunctionReferencePattern(functionName) { const identifier = sqlBareIdentifierPattern(); return String.raw`(?:${identifier}\s*\.\s*)?${escapeRegExp(functionName)}\s*(?:\([^)]*\))?`; } function sqlSpecificFunctionReferencePattern(functionName) { const identifier = sqlBareIdentifierPattern(); return String.raw`(?:${identifier}\s*\.\s*)?${escapeRegExp(functionName)}\b\s*(?:\([^)]*\))?`; } function sqlAnyFunctionReferencePattern() { const identifier = sqlBareIdentifierPattern(); return String.raw`(?:${identifier}\s*\.\s*)?${identifier}\s*(?:\([^)]*\))?`; } function sqlFunctionListContainingPattern(targetFunctionReference) { const functionReference = sqlAnyFunctionReferencePattern(); return String.raw`(?:${functionReference}\s*,\s*)*${targetFunctionReference}(?:\s*,\s*${functionReference})*`; } function staticStringLiteralAlternatives(values) { return values.flatMap((value) => { const escapedValue = escapeRegExp(value); return [`"${escapedValue}"`, `'${escapedValue}'`, `\`${escapedValue}\``]; }); } function staticPrismaPropertyAccessPattern(values) { const dotNames = values.map(escapeRegExp).join("|"); const bracketNames = staticStringLiteralAlternatives(values).join("|"); return String.raw`(?:(?:\.|\?\.)\s*(?:${dotNames})|(?:\?\.\s*)?\[\s*(?:${bracketNames})\s*\])`; } function staticPrismaPropertyAccessMatchAt(content, structure, index, values, beforeIndex = index) { const allowedValues = new Set(values); let propertyStart = index; if (structure.startsWith("?.", index)) { propertyStart = skipWhitespaceForward(structure, index + 2); } else if (structure[index] === ".") { propertyStart = skipWhitespaceForward(structure, index + 1); } if (structure[propertyStart] === "[") { const closeBracketIndex = findMatchingBracket(structure, propertyStart); if (closeBracketIndex === -1) return null; const expressionStart = propertyStart + 1; const expressionEnd = closeBracketIndex; const resolvedValues = staticJavaScriptExpressionValues( content, structure, content.slice(expressionStart, expressionEnd), expressionStart, beforeIndex, new Set(), 0 ); const value = resolvedValues.find((candidate) => allowedValues.has(candidate)); return value ? { index, end: closeBracketIndex + 1, value } : null; } if (propertyStart === index && structure[index] !== "." && !structure.startsWith("?.", index)) return null; const nameMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(propertyStart)); if (!nameMatch || !allowedValues.has(nameMatch[0])) return null; return { index, end: propertyStart + nameMatch[0].length, value: nameMatch[0] }; } function staticPrismaPropertyAccessMatches(content, structure, values, startIndex = 0, endIndex = structure.length) { const matches = []; for (let cursor = startIndex; cursor < endIndex; cursor += 1) { if (structure[cursor] !== "." && structure[cursor] !== "[" && !structure.startsWith("?.", cursor)) continue; const match = staticPrismaPropertyAccessMatchAt(content, structure, cursor, values, cursor); if (!match || match.end > endIndex) continue; // computed key 统一走静态 JS 表达式解析,覆盖多段拼接、变量 alias 和完整括号表达式。 matches.push(match); cursor = match.end - 1; } return matches; } function staticPrismaDelegateExpressionMatchAt(content, structure, index, models) { const receiverMatch = /^(?:[A-Za-z_$][\w$]*|this)\b/.exec(structure.slice(index)); if (!receiverMatch) return null; const modelAccessStart = skipWhitespaceForward(structure, index + receiverMatch[0].length); const modelAccess = staticPrismaPropertyAccessMatchAt(content, structure, modelAccessStart, models, modelAccessStart); return modelAccess ? { index, end: modelAccess.end, model: modelAccess.value } : null; } function hasOptionalCallOperatorAt(structure, index) { const callStart = skipWhitespaceForward(structure, index); if (structure.startsWith("?.", callStart)) { return skipWhitespaceForward(structure, callStart + 2); } return callStart; } function functionPrototypeMethodKind(structure, sourceStart, sourceEnd) { const start = skipWhitespaceForward(structure, sourceStart); const end = skipWhitespaceBackward(structure, sourceEnd - 1) + 1; if (start >= end) return null; const normalizedSource = structure.slice(start, end).replace(/\s+/g, ""); if (normalizedSource === "Function.prototype.call") return "call"; if (normalizedSource === "Function.prototype.apply") return "apply"; return null; } function staticBuiltinInvokerKind(structure, sourceStart, sourceEnd) { const functionPrototypeKind = functionPrototypeMethodKind(structure, sourceStart, sourceEnd); if (functionPrototypeKind) return functionPrototypeKind; const start = skipWhitespaceForward(structure, sourceStart); const end = skipWhitespaceBackward(structure, sourceEnd - 1) + 1; if (start >= end) return null; const normalizedSource = structure.slice(start, end).replace(/\s+/g, ""); return normalizedSource === "Reflect.apply" ? "reflectApply" : null; } function staticBuiltinInvokerBaseExpressionMatchAt(structure, index) { return staticBuiltinInvokerBaseExpressionMatchAtWithContent("", structure, index); } function staticBuiltinInvokerBaseExpressionMatchAtWithContent(content, structure, index) { const expressionStart = skipWhitespaceForward(structure, index); const rootMatch = /^(?:Function|Reflect)\b/.exec(structure.slice(expressionStart)); if (!rootMatch) return null; const firstAccessStart = skipWhitespaceForward(structure, expressionStart + rootMatch[0].length); const firstAccess = staticStaticMemberPropertyAccessMatchAt(content, structure, firstAccessStart); if (!firstAccess) return null; if (rootMatch[0] === "Reflect") { return firstAccess.value === "apply" ? { kind: "reflectApply", end: firstAccess.end } : null; } if (firstAccess.value !== "prototype") return null; const secondAccessStart = skipWhitespaceForward(structure, firstAccess.end); const secondAccess = staticStaticMemberPropertyAccessMatchAt(content, structure, secondAccessStart); if (!secondAccess || (secondAccess.value !== "call" && secondAccess.value !== "apply")) return null; return { kind: secondAccess.value, end: secondAccess.end }; } function staticMemberOperationMatchAt(content, structure, index, values) { const accessStart = skipWhitespaceForward(structure, index); const access = staticStaticMemberPropertyAccessMatchAt(content, structure, accessStart); if (!access || !values.includes(access.value)) return null; return { value: access.value, end: access.end }; } function staticBuiltinMemberOperationMatchAt(content, structure, index, values) { return staticMemberOperationMatchAt(content, structure, index, values); } function staticCallableMemberOperationMatchAt(content, structure, index, values) { const operation = staticMemberOperationMatchAt(content, structure, index, values); if (!operation) return null; const openParenIndex = skipOptionalTypeArguments(structure, operation.end); if (structure[openParenIndex] !== "(") return null; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) return null; return { kind: operation.value, methodNameEnd: operation.end, openParenIndex, closeParenIndex }; } function isStaticBuiltinInvokerBindReceiver(content, structure, kind, receiverArgument) { if (!receiverArgument) return false; const receiverStart = skipWhitespaceForward(structure, receiverArgument.index); const receiverEnd = skipWhitespaceBackward(structure, receiverArgument.end - 1) + 1; if (receiverStart >= receiverEnd) return false; if (kind === "reflectApply") { // Reflect.apply 的 this 不参与语义,`Reflect.apply.bind(null|undefined|Reflect)` 都等价;只要 target 是静态 Reflect.apply 就登记 alias。 return true; } const receiverMatch = staticBuiltinInvokerBaseExpressionMatchAtWithContent(content, structure, receiverStart); return receiverMatch?.kind === kind && skipWhitespaceForward(structure, receiverMatch.end) === receiverEnd; } function staticBuiltinInvokerExpressionMatchAt(content, structure, index) { const expressionStart = skipWhitespaceForward(structure, index); const baseMatch = staticBuiltinInvokerBaseExpressionMatchAtWithContent(content, structure, expressionStart); if (!baseMatch) return null; const bindMethodStart = skipWhitespaceForward(structure, baseMatch.end); const bindMethodMatch = staticBuiltinMemberOperationMatchAt(content, structure, bindMethodStart, ["bind"]); if (!bindMethodMatch) return { kind: baseMatch.kind, end: baseMatch.end, bound: false }; const bindCallStart = skipWhitespaceForward(structure, bindMethodMatch.end); if (structure[bindCallStart] !== "(") return null; const bindCallEnd = findMatchingParen(structure, bindCallStart); if (bindCallEnd === -1) return null; const receiverArgument = nthCallArgumentSource(content, structure, bindCallStart, bindCallEnd, 0); if (!isStaticBuiltinInvokerBindReceiver(content, structure, baseMatch.kind, receiverArgument)) return null; return { kind: baseMatch.kind, end: bindCallEnd + 1, bound: true }; } function findStaticBuiltinInvokerAliases(content, structure) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const invokerMatch = staticBuiltinInvokerExpressionMatchAt(content, structure, initializerStart); if (!invokerMatch) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, invokerMatch.end, statementEnd); if (aliasEnd === -1) continue; // 只接受同文件静态内建调用器 alias,复杂表达式不进入 scope gate 语义展开。 aliases.push({ name: declarationMatch[1], kind: invokerMatch.kind, bound: invokerMatch.bound, index: declarationMatch.index, end: aliasEnd }); } for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const variableName = assignmentMatch[1]; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; if (/\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, assignmentMatch.index - 16), variableIndex))) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const invokerMatch = staticBuiltinInvokerExpressionMatchAt(content, structure, initializerStart); if (!invokerMatch) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, invokerMatch.end, statementEnd); if (aliasEnd === -1) continue; // 赋值 alias 覆盖 `let c; c = Function.prototype.call` 这类后续绑定。 aliases.push({ name: variableName, kind: invokerMatch.kind, bound: invokerMatch.bound, index: assignmentMatch.index, end: aliasEnd }); } return propagateStaticIdentifierAliases(structure, aliases, (sourceAlias, initializer) => ({ name: initializer.name, kind: sourceAlias.kind, bound: sourceAlias.bound, index: initializer.index, end: initializer.end })); } function hasSimpleIdentifierAssignmentBetween(structure, variableName, startIndex, beforeIndex) { const assignmentPattern = new RegExp(String.raw`\b${escapeRegExp(variableName)}\b\s*=`, "g"); for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number" || assignmentMatch.index <= startIndex || assignmentMatch.index >= beforeIndex) continue; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1 || equalsIndex >= beforeIndex) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; return true; } return false; } function latestStaticAliasRecordAt(structure, aliases, aliasName, beforeIndex) { const latestAlias = aliases .filter((alias) => alias.name === aliasName && (alias.end ?? alias.index) < beforeIndex) .toSorted((left, right) => (right.end ?? right.index) - (left.end ?? left.index))[0]; if (!latestAlias) return null; if (hasSimpleIdentifierAssignmentBetween(structure, aliasName, latestAlias.end ?? latestAlias.index, beforeIndex)) return null; return latestAlias; } function simpleIdentifierAliasInitializers(structure) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const initializerMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(initializerStart)); if (!initializerMatch) continue; const initializerEnd = initializerStart + initializerMatch[0].length; const statementEnd = findStatementEnd(structure, initializerStart); const aliasEnd = simpleStaticAliasInitializerEnd(structure, initializerEnd, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: declarationMatch[1], initializerName: initializerMatch[0], index: declarationMatch.index, initializerStart, end: aliasEnd }); } for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const variableName = assignmentMatch[1]; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; if (/\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, assignmentMatch.index - 16), variableIndex))) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const initializerMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(initializerStart)); if (!initializerMatch) continue; const initializerEnd = initializerStart + initializerMatch[0].length; const statementEnd = findStatementEnd(structure, initializerStart); const aliasEnd = simpleStaticAliasInitializerEnd(structure, initializerEnd, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: variableName, initializerName: initializerMatch[0], index: assignmentMatch.index, initializerStart, end: aliasEnd }); } return aliases; } function propagateStaticIdentifierAliases(structure, aliases, copyAlias) { const propagated = [...aliases]; const initializers = simpleIdentifierAliasInitializers(structure); for (let depth = 0; depth < rawSqlVariableResolutionMaxDepth; depth += 1) { let changed = false; for (const initializer of initializers) { if (initializer.name === initializer.initializerName) continue; if (propagated.some((alias) => alias.name === initializer.name && (alias.end ?? alias.index) === initializer.end)) continue; const sourceAlias = latestStaticAliasRecordAt(structure, propagated, initializer.initializerName, initializer.initializerStart); if (!sourceAlias) continue; propagated.push(copyAlias(sourceAlias, initializer)); changed = true; } if (!changed) break; } return propagated.toSorted((left, right) => (left.end ?? left.index) - (right.end ?? right.index)); } function completeStaticWrapperSourceMatch(content, structure, sourceMatcher, sourceStart, sourceEnd, beforeIndex) { const expressionStart = skipWhitespaceForward(structure, sourceStart); const expressionEnd = skipWhitespaceBackward(structure, sourceEnd - 1) + 1; if (expressionStart >= expressionEnd) return null; const match = sourceMatcher(content, structure, expressionStart, expressionEnd, beforeIndex); if (!match) return null; return isAtStaticWrapperExpressionEnd(structure, match.end, expressionEnd) ? match : null; } function staticIdentifierAliasSourceMatchAt(structure, aliases, sourceStart, sourceEnd, beforeIndex) { const aliasName = simpleIdentifierSourceName(structure, sourceStart, sourceEnd); if (!aliasName) return null; const alias = latestStaticAliasRecordAt(structure, aliases, aliasName, beforeIndex); return alias ? { index: sourceStart, end: sourceEnd, alias } : null; } function isAtStaticWrapperExpressionEnd(structure, cursor, expressionEnd) { // wrapper source 已按表达式边界 trim;结束判断不能继续越过 `}`、`]` 或 `;`。 return cursor === expressionEnd || (cursor < expressionEnd && skipWhitespaceForward(structure, cursor) === expressionEnd); } function staticWrapperSourcesInExpression(content, structure, sourceMatcher, expressionStart, expressionEnd, beforeIndex, basePath, depth) { if (depth >= rawSqlVariableResolutionMaxDepth) return []; const wrappers = []; const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, expressionStart, expressionEnd); const valueStart = skipWhitespaceForward(structure, unwrapped.start); const valueEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (valueStart >= valueEnd) return wrappers; const sourceMatch = completeStaticWrapperSourceMatch(content, structure, sourceMatcher, valueStart, valueEnd, beforeIndex); if (sourceMatch) { wrappers.push({ path: basePath, source: sourceMatch }); return wrappers; } const objectAssignSources = staticObjectAssignArgumentSources(content, structure, valueStart, valueEnd); if (objectAssignSources.length > 0) { for (const objectAssignSource of objectAssignSources) { wrappers.push( ...staticWrapperSourcesInExpression( content, structure, sourceMatcher, objectAssignSource.index, objectAssignSource.end, beforeIndex, basePath, depth + 1 ) ); } return wrappers; } if (structure[valueStart] === "{") { const objectEnd = findMatchingBrace(structure, valueStart); if (objectEnd === -1 || objectEnd !== valueEnd - 1) return wrappers; for (const part of destructuringParts(structure, valueStart + 1, objectEnd)) { const propertyName = staticDestructuringPropertyKeyValue(content, structure, part.start, part.end, beforeIndex); if (!propertyName) continue; const keyStart = skipWhitespaceForward(structure, part.start); const keyEnd = destructuringPropertyKeyEnd(structure, keyStart, part.end); if (keyEnd === -1) continue; const colonIndex = skipWhitespaceForward(structure, keyEnd); if (structure[colonIndex] !== ":") { const shorthandMatch = completeStaticWrapperSourceMatch(content, structure, sourceMatcher, keyStart, keyEnd, beforeIndex); if (shorthandMatch) wrappers.push({ path: basePath.concat(propertyName), source: shorthandMatch }); continue; } wrappers.push( ...staticWrapperSourcesInExpression( content, structure, sourceMatcher, skipWhitespaceForward(structure, colonIndex + 1), part.end, beforeIndex, basePath.concat(propertyName), depth + 1 ) ); } return wrappers; } if (structure[valueStart] === "[") { const arrayEnd = findMatchingBracket(structure, valueStart); if (arrayEnd === -1 || arrayEnd !== valueEnd - 1) return wrappers; const parts = destructuringParts(structure, valueStart + 1, arrayEnd); for (const [elementIndex, part] of parts.entries()) { wrappers.push( ...staticWrapperSourcesInExpression( content, structure, sourceMatcher, part.start, part.end, beforeIndex, basePath.concat(elementIndex), depth + 1 ) ); } } return wrappers; } function staticObjectAssignArgumentSources(content, structure, sourceStart, sourceEnd) { if (!structure.startsWith("Object", sourceStart)) return []; const assignAccess = staticStaticMemberPropertyAccessMatchAt(content, structure, sourceStart + "Object".length); if (!assignAccess || assignAccess.value !== "assign") return []; const openParenIndex = skipWhitespaceForward(structure, assignAccess.end); if (structure[openParenIndex] !== "(") return []; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1 || skipWhitespaceForward(structure, closeParenIndex + 1) !== sourceEnd) return []; const sources = []; let argumentIndex = 1; while (true) { const argument = nthCallArgumentSource(content, structure, openParenIndex, closeParenIndex, argumentIndex); if (!argument) break; sources.push(argument); argumentIndex += 1; } return sources; } function staticObjectWrapperSources(content, structure, sourceMatcher) { const wrappers = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const expressionEnd = skipWhitespaceBackward(structure, statementEnd - 1) + 1; for (const wrapper of staticWrapperSourcesInExpression( content, structure, sourceMatcher, initializerStart, expressionEnd, declarationMatch.index, [], 0 )) { if (wrapper.path.length === 0) continue; wrappers.push({ name: declarationMatch[1], path: wrapper.path, kind: "path", index: declarationMatch.index, end: statementEnd, source: wrapper.source }); } } return wrappers; } function staticArrayWrapperSources(_content, _structure, _sourceMatcher) { // 数组 wrapper 已由 staticObjectWrapperSources 的 path-source 递归统一覆盖。 return []; } function staticFunctionReturnWrapperSources(content, structure, sourceMatcher) { const wrappers = []; const functionPattern = /\bfunction\s+([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{/g; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; for (const functionMatch of structure.matchAll(functionPattern)) { if (typeof functionMatch.index !== "number") continue; const openBraceIndex = structure.indexOf("{", functionMatch.index); const closeBraceIndex = openBraceIndex === -1 ? -1 : findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) continue; const bodyStart = openBraceIndex + 1; const bodyEnd = closeBraceIndex; const returnPattern = /\breturn\b/g; returnPattern.lastIndex = bodyStart; const returnMatch = returnPattern.exec(structure); if (!returnMatch || returnMatch.index >= bodyEnd) continue; const expressionStart = skipWhitespaceForward(structure, returnMatch.index + returnMatch[0].length); const statementEnd = Math.min(findStatementEnd(structure, expressionStart), bodyEnd); wrappers.push( ...staticFunctionReturnExpressionWrapperSources( content, structure, sourceMatcher, functionMatch[1], functionMatch.index, closeBraceIndex + 1, expressionStart, statementEnd ) ); } for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const functionExpressionMatch = /^function\b\s*(?:[A-Za-z_$][\w$]*)?\s*\([^)]*\)\s*\{/.exec(structure.slice(initializerStart)); if (functionExpressionMatch) { const openBraceIndex = structure.indexOf("{", initializerStart + functionExpressionMatch[0].length - 1); const closeBraceIndex = openBraceIndex === -1 ? -1 : findMatchingBrace(structure, openBraceIndex); const statementEnd = closeBraceIndex === -1 ? -1 : findStatementEnd(structure, closeBraceIndex + 1); if (closeBraceIndex !== -1 && statementEnd !== -1) { const returnPattern = /\breturn\b/g; returnPattern.lastIndex = openBraceIndex + 1; const returnMatch = returnPattern.exec(structure); if (returnMatch && returnMatch.index < closeBraceIndex) { const expressionStart = skipWhitespaceForward(structure, returnMatch.index + returnMatch[0].length); const expressionEnd = Math.min(findStatementEnd(structure, expressionStart), closeBraceIndex); const aliasEnd = simpleStaticAliasInitializerEnd(structure, closeBraceIndex + 1, statementEnd); if (aliasEnd !== -1) { wrappers.push( ...staticFunctionReturnExpressionWrapperSources( content, structure, sourceMatcher, declarationMatch[1], declarationMatch.index, aliasEnd, expressionStart, expressionEnd ) ); } } } continue; } let statementEnd = findStatementEnd(structure, initializerStart); const afterArrow = staticArrowExpressionBodyStart(structure, initializerStart, statementEnd); if (afterArrow === -1) continue; if (structure[afterArrow] === "{") { const closeBraceIndex = findMatchingBrace(structure, afterArrow); if (closeBraceIndex !== -1) statementEnd = findStatementEnd(structure, closeBraceIndex + 1); const aliasEnd = closeBraceIndex === -1 ? -1 : simpleStaticAliasInitializerEnd(structure, closeBraceIndex + 1, statementEnd); if (closeBraceIndex === -1 || aliasEnd === -1) continue; wrappers.push( ...staticBlockReturnWrapperSources( content, structure, sourceMatcher, declarationMatch[1], declarationMatch.index, aliasEnd, afterArrow ) ); continue; } const expressionEnd = skipWhitespaceBackward(structure, statementEnd - 1) + 1; const aliasEnd = simpleStaticAliasInitializerEnd(structure, expressionEnd, statementEnd); if (aliasEnd === -1) continue; wrappers.push( ...staticFunctionReturnExpressionWrapperSources( content, structure, sourceMatcher, declarationMatch[1], declarationMatch.index, aliasEnd, afterArrow, expressionEnd ) ); } return wrappers; } function staticBlockReturnWrapperSources(content, structure, sourceMatcher, functionName, functionIndex, functionEnd, openBraceIndex) { const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) return []; const bodyStart = openBraceIndex + 1; const bodyEnd = closeBraceIndex; const returnPattern = /\breturn\b/g; returnPattern.lastIndex = bodyStart; const returnMatch = returnPattern.exec(structure); if (!returnMatch || returnMatch.index >= bodyEnd) return []; const nextReturn = returnPattern.exec(structure); if (nextReturn && nextReturn.index < bodyEnd) return []; const expressionStart = skipWhitespaceForward(structure, returnMatch.index + returnMatch[0].length); const statementEnd = Math.min(findStatementEnd(structure, expressionStart), bodyEnd); return staticFunctionReturnExpressionWrapperSources( content, structure, sourceMatcher, functionName, functionIndex, functionEnd, expressionStart, statementEnd ); } function staticArrowExpressionBodyStart(structure, initializerStart, statementEnd) { const arrowStart = skipWhitespaceForward(structure, initializerStart); let cursor = arrowStart; if (structure[cursor] === "(") { const paramsEnd = findMatchingParen(structure, cursor); if (paramsEnd === -1 || paramsEnd >= statementEnd) return -1; cursor = skipWhitespaceForward(structure, paramsEnd + 1); } else { const identifierMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(cursor)); if (!identifierMatch) return -1; cursor = skipWhitespaceForward(structure, cursor + identifierMatch[0].length); } if (structure[cursor] === ":") { cursor = staticTypeAnnotationArrowIndex(structure, cursor + 1, statementEnd); if (cursor === -1) return -1; } cursor = skipWhitespaceForward(structure, cursor); if (!structure.startsWith("=>", cursor)) return -1; return skipWhitespaceForward(structure, cursor + 2); } function staticTypeAnnotationArrowIndex(structure, sourceStart, sourceEnd) { let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; for (let cursor = sourceStart; cursor < sourceEnd - 1; cursor += 1) { const char = structure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth = Math.max(0, parenDepth - 1); if (char === "{") braceDepth += 1; if (char === "}") braceDepth = Math.max(0, braceDepth - 1); if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1); if (structure.startsWith("=>", cursor) && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) { return cursor; } } return -1; } function staticFunctionReturnExpressionWrapperSources( content, structure, sourceMatcher, functionName, functionIndex, functionEnd, expressionStart, expressionEnd ) { // 函数返回 object/array 时保留内部静态 path,供 getBox().raw 与 const { raw } = getBox() 共用。 return staticWrapperSourcesInExpression(content, structure, sourceMatcher, expressionStart, expressionEnd, functionIndex, [], 0).map( (wrapper) => ({ kind: wrapper.path.length === 0 ? "functionReturn" : "functionReturnPath", name: functionName, path: wrapper.path, index: functionIndex, end: functionEnd, source: wrapper.source }) ); } function staticWrapperSources(content, structure, sourceMatcher) { return staticObjectWrapperSources(content, structure, sourceMatcher) .concat(staticArrayWrapperSources(content, structure, sourceMatcher)) .concat(staticFunctionReturnWrapperSources(content, structure, sourceMatcher)) .toSorted((left, right) => left.end - right.end); } function isStaticWrapperActiveAt(structure, wrapper, beforeIndex) { if (wrapper.end >= beforeIndex) return false; return !hasSimpleIdentifierAssignmentBetween(structure, wrapper.name, wrapper.end, beforeIndex); } function staticArrayIndexAccessMatchAt(structure, index, expectedIndex) { let bracketStart = skipWhitespaceForward(structure, index); if (structure.startsWith("?.", bracketStart)) { bracketStart = skipWhitespaceForward(structure, bracketStart + 2); } if (structure[bracketStart] !== "[") return null; const bracketEnd = findMatchingBracket(structure, bracketStart); if (bracketEnd === -1) return null; const indexSource = structure.slice(bracketStart + 1, bracketEnd).trim(); if (!/^\d+$/.test(indexSource) || Number.parseInt(indexSource, 10) !== expectedIndex) return null; return { index, end: bracketEnd + 1 }; } function staticWrapperPathAccessMatchAt(content, structure, index, path) { let cursor = index; for (const segment of path) { const accessStart = skipWhitespaceForward(structure, cursor); if (typeof segment === "number") { const arrayAccess = staticArrayIndexAccessMatchAt(structure, accessStart, segment); if (!arrayAccess) return null; cursor = arrayAccess.end; continue; } const propertyAccess = staticPrismaPropertyAccessMatchAt(content, structure, accessStart, [segment], accessStart); if (!propertyAccess) return null; cursor = propertyAccess.end; } return { index, end: cursor }; } function staticWrapperSourceUsageMatchAt(content, structure, wrappers, index, beforeIndex = index) { const usageStart = skipWhitespaceForward(structure, index); const nameMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(usageStart)); if (!nameMatch || isMemberIdentifierReference(structure, usageStart)) return null; const name = nameMatch[0]; const afterName = skipWhitespaceForward(structure, usageStart + name.length); const candidates = wrappers.filter((wrapper) => wrapper.name === name && isStaticWrapperActiveAt(structure, wrapper, beforeIndex)); for (const wrapper of candidates.toSorted((left, right) => right.end - left.end)) { if (wrapper.kind === "path") { const pathAccess = staticWrapperPathAccessMatchAt(content, structure, afterName, wrapper.path); if (pathAccess) return { index: usageStart, end: pathAccess.end, wrapper }; continue; } if (wrapper.kind === "functionReturn" || wrapper.kind === "functionReturnPath") { const callEnd = staticZeroArgumentCallEndAt(content, structure, afterName); if (callEnd === -1) continue; if (wrapper.kind === "functionReturn") return { index: usageStart, end: callEnd, wrapper }; const pathAccess = staticWrapperPathAccessMatchAt(content, structure, callEnd, wrapper.path); if (pathAccess) return { index: usageStart, end: pathAccess.end, wrapper }; } } return null; } function staticZeroArgumentCallEndAt(content, structure, index) { let openParenIndex = skipWhitespaceForward(structure, index); if (structure.startsWith("?.", openParenIndex)) { openParenIndex = skipWhitespaceForward(structure, openParenIndex + 2); } if (structure[openParenIndex] !== "(") return -1; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) return -1; return nthCallArgumentSource(content, structure, openParenIndex, closeParenIndex, 0) ? -1 : closeParenIndex + 1; } function staticWrapperSourceUsageMatches(content, structure, wrappers) { const matches = []; const names = [...new Set(wrappers.map((wrapper) => wrapper.name))]; if (names.length === 0) return matches; const namePattern = new RegExp(String.raw`\b(${names.map(escapeRegExp).join("|")})\b`, "g"); for (const match of structure.matchAll(namePattern)) { if (typeof match.index !== "number") continue; const usage = staticWrapperSourceUsageMatchAt(content, structure, wrappers, match.index, match.index); if (usage) matches.push(usage); } return matches.toSorted((left, right) => left.index - right.index); } function findStaticWrapperUsageAliases(content, structure, wrappers) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const usage = staticWrapperSourceUsageMatchAt(content, structure, wrappers, initializerStart, declarationMatch.index); if (!usage) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, usage.end, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: aliasEnd }); } for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const variableName = assignmentMatch[1]; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; if (/\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, assignmentMatch.index - 16), variableIndex))) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const usage = staticWrapperSourceUsageMatchAt(content, structure, wrappers, initializerStart, assignmentMatch.index); if (!usage) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, usage.end, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: variableName, index: assignmentMatch.index, end: aliasEnd }); } return aliases; } function pathsEqual(left, right) { return left.length === right.length && left.every((value, index) => value === right[index]); } function activeWrapperForPath(structure, wrappers, objectName, path, beforeIndex) { return wrappers .filter( (wrapper) => wrapper.kind === "path" && wrapper.name === objectName && pathsEqual(wrapper.path, path) && isStaticWrapperActiveAt(structure, wrapper, beforeIndex) ) .toSorted((left, right) => right.end - left.end)[0]; } function activeFunctionReturnWrapperForPath(structure, wrappers, functionName, path, beforeIndex) { return wrappers .filter( (wrapper) => wrapper.kind === "functionReturnPath" && wrapper.name === functionName && pathsEqual(wrapper.path, path) && isStaticWrapperActiveAt(structure, wrapper, beforeIndex) ) .toSorted((left, right) => right.end - left.end)[0]; } function activeWrapperForDestructuringSource(content, structure, wrappers, sourceStart, path, beforeIndex) { const nameMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(sourceStart)); if (!nameMatch || isMemberIdentifierReference(structure, sourceStart)) return null; const sourceName = nameMatch[0]; const afterName = skipWhitespaceForward(structure, sourceStart + sourceName.length); const callEnd = staticZeroArgumentCallEndAt(content, structure, afterName); if (callEnd !== -1) { const wrapper = activeFunctionReturnWrapperForPath(structure, wrappers, sourceName, path, beforeIndex); return wrapper ? { wrapper, end: callEnd } : null; } const wrapper = activeWrapperForPath(structure, wrappers, sourceName, path, beforeIndex); return wrapper ? { wrapper, end: sourceStart + sourceName.length } : null; } function destructuringAliasBindingsForWrapperPaths(content, structure, bindingStart, bindingEnd, beforeIndex, basePath = []) { const bindings = []; for (const part of destructuringParts(structure, bindingStart, bindingEnd)) { const propertyName = staticDestructuringPropertyKeyValue(content, structure, part.start, part.end, beforeIndex); if (!propertyName) continue; const keyStart = skipWhitespaceForward(structure, part.start); const keyEnd = destructuringPropertyKeyEnd(structure, keyStart, part.end); if (keyEnd === -1) continue; const path = basePath.concat(propertyName); const afterKey = skipWhitespaceForward(structure, keyEnd); if (structure[afterKey] === ":") { const valueStart = skipWhitespaceForward(structure, afterKey + 1); if (structure[valueStart] === "{") { const nestedEnd = findMatchingBrace(structure, valueStart); if (nestedEnd !== -1 && nestedEnd < part.end) { bindings.push(...destructuringAliasBindingsForWrapperPaths(content, structure, valueStart + 1, nestedEnd, beforeIndex, path)); } continue; } const aliasName = destructuringAliasName(structure, part.start, part.end); if (aliasName) bindings.push({ aliasName, path }); continue; } const aliasName = destructuringAliasName(structure, part.start, part.end); if (aliasName) bindings.push({ aliasName, path }); } return bindings; } function arrayDestructuringAliasBindingsForWrapperPaths(structure, bindingStart, bindingEnd, basePath = []) { const bindings = []; const parts = destructuringParts(structure, bindingStart, bindingEnd); for (const [elementIndex, part] of parts.entries()) { const valueStart = skipWhitespaceForward(structure, part.start); const valueEnd = skipWhitespaceBackward(structure, part.end - 1) + 1; if (valueStart >= valueEnd) continue; const path = basePath.concat(elementIndex); if (structure[valueStart] === "[") { const nestedEnd = findMatchingBracket(structure, valueStart); if (nestedEnd !== -1 && nestedEnd === valueEnd - 1) { bindings.push(...arrayDestructuringAliasBindingsForWrapperPaths(structure, valueStart + 1, nestedEnd, path)); } continue; } const aliasMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(valueStart, valueEnd)); if (!aliasMatch) continue; const aliasEnd = valueStart + aliasMatch[0].length; if (skipWhitespaceForward(structure, aliasEnd) !== valueEnd) continue; bindings.push({ aliasName: aliasMatch[0], path }); } return bindings; } function findStaticWrapperDestructuredAliases(content, structure, wrappers) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s*\{/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const openBraceIndex = structure.indexOf("{", declarationMatch.index); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] === ":") { const equalsAfterType = structure.indexOf("=", cursor + 1); const semicolonAfterType = structure.indexOf(";", cursor + 1); if (equalsAfterType === -1 || (semicolonAfterType !== -1 && semicolonAfterType < equalsAfterType)) continue; cursor = skipWhitespaceForward(structure, equalsAfterType + 1); } else { if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); } const statementEnd = findStatementEnd(structure, cursor); for (const binding of destructuringAliasBindingsForWrapperPaths(content, structure, openBraceIndex + 1, closeBraceIndex, declarationMatch.index)) { const source = activeWrapperForDestructuringSource(content, structure, wrappers, cursor, binding.path, declarationMatch.index); if (!source) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, source.end, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: binding.aliasName, index: declarationMatch.index, end: aliasEnd }); } } return aliases; } function findStaticWrapperArrayDestructuredAliases(content, structure, wrappers) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s*\[/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const openBracketIndex = structure.indexOf("[", declarationMatch.index); if (openBracketIndex === -1) continue; const closeBracketIndex = findMatchingBracket(structure, openBracketIndex); if (closeBracketIndex === -1) continue; let cursor = skipWhitespaceForward(structure, closeBracketIndex + 1); if (structure[cursor] === ":") { const equalsAfterType = structure.indexOf("=", cursor + 1); const semicolonAfterType = structure.indexOf(";", cursor + 1); if (equalsAfterType === -1 || (semicolonAfterType !== -1 && semicolonAfterType < equalsAfterType)) continue; cursor = skipWhitespaceForward(structure, equalsAfterType + 1); } else { if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); } const statementEnd = findStatementEnd(structure, cursor); for (const binding of arrayDestructuringAliasBindingsForWrapperPaths(structure, openBracketIndex + 1, closeBracketIndex)) { const source = activeWrapperForDestructuringSource(content, structure, wrappers, cursor, binding.path, declarationMatch.index); if (!source) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, source.end, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: binding.aliasName, index: declarationMatch.index, end: aliasEnd }); } } return aliases; } function findStaticWrapperAssignedDestructuredAliases(content, structure, wrappers) { const aliases = []; const assignmentPattern = /\(\s*\{/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const openParenIndex = structure.indexOf("(", assignmentMatch.index); if (openParenIndex === -1 || !isParenthesizedExpressionOpen(structure, openParenIndex)) continue; const openBraceIndex = structure.indexOf("{", openParenIndex); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeBraceIndex === -1 || closeParenIndex === -1 || closeBraceIndex > closeParenIndex) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); const statementEnd = findStatementEnd(structure, openParenIndex); for (const binding of destructuringAliasBindingsForWrapperPaths(content, structure, openBraceIndex + 1, closeBraceIndex, assignmentMatch.index)) { const source = activeWrapperForDestructuringSource(content, structure, wrappers, cursor, binding.path, assignmentMatch.index); if (!source || skipWhitespaceForward(structure, source.end) !== closeParenIndex) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, closeParenIndex + 1, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: binding.aliasName, index: assignmentMatch.index, end: aliasEnd }); } } return aliases; } function findStaticWrapperAssignedArrayDestructuredAliases(content, structure, wrappers) { const aliases = []; const assignmentPattern = /\(\s*\[/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const openParenIndex = structure.indexOf("(", assignmentMatch.index); if (openParenIndex === -1 || !isParenthesizedExpressionOpen(structure, openParenIndex)) continue; const openBracketIndex = structure.indexOf("[", openParenIndex); if (openBracketIndex === -1) continue; const closeBracketIndex = findMatchingBracket(structure, openBracketIndex); const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeBracketIndex === -1 || closeParenIndex === -1 || closeBracketIndex > closeParenIndex) continue; let cursor = skipWhitespaceForward(structure, closeBracketIndex + 1); if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); const statementEnd = findStatementEnd(structure, openParenIndex); for (const binding of arrayDestructuringAliasBindingsForWrapperPaths(structure, openBracketIndex + 1, closeBracketIndex)) { const source = activeWrapperForDestructuringSource(content, structure, wrappers, cursor, binding.path, assignmentMatch.index); if (!source || skipWhitespaceForward(structure, source.end) !== closeParenIndex) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, closeParenIndex + 1, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: binding.aliasName, index: assignmentMatch.index, end: aliasEnd }); } } return aliases; } function findStaticWrapperExtractionAliases(content, structure, wrappers) { return findStaticWrapperUsageAliases(content, structure, wrappers) .concat(findStaticWrapperDestructuredAliases(content, structure, wrappers)) .concat(findStaticWrapperArrayDestructuredAliases(content, structure, wrappers)) .concat(findStaticWrapperAssignedDestructuredAliases(content, structure, wrappers)) .concat(findStaticWrapperAssignedArrayDestructuredAliases(content, structure, wrappers)) .toSorted((left, right) => left.end - right.end); } function staticBuiltinInvokerAliasAt(structure, aliases, aliasName, beforeIndex) { return latestStaticAliasRecordAt(structure, aliases, aliasName, beforeIndex); } function staticBuiltinInvokerAliasKindAt(structure, aliases, aliasName, beforeIndex) { return staticBuiltinInvokerAliasAt(structure, aliases, aliasName, beforeIndex)?.kind ?? null; } function aliasDirectInvocationCallMatches(structure, aliasNames) { const uniqueAliasNames = [...new Set(aliasNames)]; if (uniqueAliasNames.length === 0) return []; const matches = []; const aliasPattern = new RegExp(String.raw`\b(${uniqueAliasNames.map(escapeRegExp).join("|")})\b`, "g"); for (const match of structure.matchAll(aliasPattern)) { if (typeof match.index !== "number") continue; const aliasName = match[1]; const aliasIndex = match.index + match[0].indexOf(aliasName); if (isMemberIdentifierReference(structure, aliasIndex)) continue; let openParenIndex = skipWhitespaceForward(structure, aliasIndex + aliasName.length); if (structure.startsWith("?.", openParenIndex)) { openParenIndex = skipWhitespaceForward(structure, openParenIndex + 2); } if (structure[openParenIndex] !== "(") continue; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ aliasName, aliasIndex, openParenIndex, closeParenIndex }); } return matches; } function functionPrototypeMethodKindFromArgument(content, structure, argument, aliases) { if (!argument) return null; const directKind = functionPrototypeMethodKind(structure, argument.index, argument.end); if (directKind) return directKind; const aliasName = simpleIdentifierSourceName(structure, argument.index, argument.end); if (!aliasName) return null; const aliasKind = staticBuiltinInvokerAliasKindAt(structure, aliases, aliasName, argument.index); return aliasKind === "call" || aliasKind === "apply" ? aliasKind : null; } function functionPrototypeCallInvocationMatches(content, structure, aliases) { const matches = []; const directCallPattern = /\bFunction\b/g; for (const match of structure.matchAll(directCallPattern)) { if (typeof match.index !== "number") continue; const baseMatch = staticBuiltinInvokerBaseExpressionMatchAtWithContent(content, structure, match.index); if (!baseMatch || (baseMatch.kind !== "call" && baseMatch.kind !== "apply")) continue; const operation = staticBuiltinMemberOperationMatchAt(content, structure, baseMatch.end, ["call", "apply"]); if (!operation) continue; const openParenIndex = skipWhitespaceForward(structure, operation.end); if (structure[openParenIndex] !== "(") continue; const closeParenIndex = openParenIndex === -1 ? -1 : findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ index: match.index, methodKind: baseMatch.kind, mode: operation.value, openParenIndex, closeParenIndex }); } const aliasNames = [...new Set(aliases.filter((alias) => (alias.kind === "call" || alias.kind === "apply") && !alias.bound).map((alias) => alias.name))]; if (aliasNames.length > 0) { const aliasCallPattern = new RegExp(String.raw`\b(${aliasNames.map(escapeRegExp).join("|")})\b`, "g"); for (const match of structure.matchAll(aliasCallPattern)) { if (typeof match.index !== "number") continue; const aliasName = match[1]; const aliasIndex = match.index + match[0].indexOf(aliasName); if (isMemberIdentifierReference(structure, aliasIndex)) continue; const aliasKind = staticBuiltinInvokerAliasKindAt(structure, aliases, aliasName, aliasIndex); if (aliasKind !== "call" && aliasKind !== "apply") continue; const operation = staticBuiltinMemberOperationMatchAt(content, structure, aliasIndex + aliasName.length, ["call", "apply"]); if (!operation) continue; const openParenIndex = skipWhitespaceForward(structure, operation.end); if (structure[openParenIndex] !== "(") continue; const closeParenIndex = openParenIndex === -1 ? -1 : findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ index: aliasIndex, methodKind: aliasKind, mode: operation.value, openParenIndex, closeParenIndex }); } } const boundAliasNames = [...new Set(aliases.filter((alias) => (alias.kind === "call" || alias.kind === "apply") && alias.bound).map((alias) => alias.name))]; if (boundAliasNames.length === 0) return matches.toSorted((left, right) => left.index - right.index); for (const match of aliasDirectInvocationCallMatches(structure, boundAliasNames)) { const { aliasName, aliasIndex, openParenIndex, closeParenIndex } = match; const alias = staticBuiltinInvokerAliasAt(structure, aliases, aliasName, aliasIndex); if (!alias?.bound || (alias.kind !== "call" && alias.kind !== "apply")) continue; matches.push({ index: aliasIndex, methodKind: alias.kind, mode: "call", openParenIndex, closeParenIndex }); } return matches.toSorted((left, right) => left.index - right.index); } function functionPrototypeEquivalentArgumentSource(content, structure, invocation, argumentIndex) { if (invocation.mode === "call") { return nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, argumentIndex); } if (invocation.mode === "apply") { if (argumentIndex === 0) { return nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, 0); } const arrayArgument = nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, 1); return nthArrayElementSource(content, structure, arrayArgument, argumentIndex - 1); } return null; } function functionPrototypeBoundTargetExpressionMatchAt(content, structure, index) { const expressionStart = skipWhitespaceForward(structure, index); const baseMatch = staticBuiltinInvokerBaseExpressionMatchAtWithContent(content, structure, expressionStart); if (!baseMatch || (baseMatch.kind !== "call" && baseMatch.kind !== "apply")) return null; const bindMethodStart = skipWhitespaceForward(structure, baseMatch.end); const bindMethodMatch = staticBuiltinMemberOperationMatchAt(content, structure, bindMethodStart, ["bind"]); if (!bindMethodMatch) return null; const bindCallStart = skipWhitespaceForward(structure, bindMethodMatch.end); if (structure[bindCallStart] !== "(") return null; const bindCallEnd = findMatchingParen(structure, bindCallStart); if (bindCallEnd === -1) return null; const targetArgument = nthCallArgumentSource(content, structure, bindCallStart, bindCallEnd, 0); if (!targetArgument) return null; return { methodKind: baseMatch.kind, targetArgument, end: bindCallEnd + 1 }; } function findFunctionPrototypeBoundTargetAliases(content, structure) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const boundTargetMatch = functionPrototypeBoundTargetExpressionMatchAt(content, structure, initializerStart); if (!boundTargetMatch) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, boundTargetMatch.end, statementEnd); if (aliasEnd === -1) continue; // 绑定 forbidden target 的 wrapper alias 只跟踪简单同文件声明,复杂重赋值保持未知。 aliases.push({ name: declarationMatch[1], methodKind: boundTargetMatch.methodKind, targetArgument: boundTargetMatch.targetArgument, index: declarationMatch.index, end: aliasEnd }); } for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const variableName = assignmentMatch[1]; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; if (/\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, assignmentMatch.index - 16), variableIndex))) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const boundTargetMatch = functionPrototypeBoundTargetExpressionMatchAt(content, structure, initializerStart); if (!boundTargetMatch) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, boundTargetMatch.end, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: variableName, methodKind: boundTargetMatch.methodKind, targetArgument: boundTargetMatch.targetArgument, index: assignmentMatch.index, end: aliasEnd }); } return propagateStaticIdentifierAliases(structure, aliases, (sourceAlias, initializer) => ({ name: initializer.name, methodKind: sourceAlias.methodKind, targetArgument: sourceAlias.targetArgument, index: initializer.index, end: initializer.end })); } function functionPrototypeBoundTargetAliasAt(structure, aliases, aliasName, beforeIndex) { return latestStaticAliasRecordAt(structure, aliases, aliasName, beforeIndex); } function functionPrototypeBoundTargetInvocationMatches(content, structure, aliases) { const matches = []; const directPattern = /\bFunction\b/g; for (const match of structure.matchAll(directPattern)) { if (typeof match.index !== "number") continue; const boundTargetMatch = functionPrototypeBoundTargetExpressionMatchAt(content, structure, match.index); if (!boundTargetMatch) continue; const openParenIndex = skipWhitespaceForward(structure, boundTargetMatch.end); if (structure[openParenIndex] !== "(") continue; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ index: match.index, methodKind: boundTargetMatch.methodKind, targetArgument: boundTargetMatch.targetArgument, openParenIndex, closeParenIndex }); } const aliasNames = [...new Set(aliases.map((alias) => alias.name))]; if (aliasNames.length === 0) return matches; for (const match of aliasDirectInvocationCallMatches(structure, aliasNames)) { const { aliasName, aliasIndex, openParenIndex, closeParenIndex } = match; const alias = functionPrototypeBoundTargetAliasAt(structure, aliases, aliasName, aliasIndex); if (!alias) continue; matches.push({ index: aliasIndex, methodKind: alias.methodKind, targetArgument: alias.targetArgument, openParenIndex, closeParenIndex }); } return matches.toSorted((left, right) => left.index - right.index); } function reflectApplyInvocationMatches(content, structure, aliases) { const matches = []; const directReflectApplyPattern = /\bReflect\b/g; for (const match of structure.matchAll(directReflectApplyPattern)) { if (typeof match.index !== "number") continue; const baseMatch = staticBuiltinInvokerBaseExpressionMatchAtWithContent(content, structure, match.index); if (!baseMatch || baseMatch.kind !== "reflectApply") continue; const operation = staticBuiltinMemberOperationMatchAt(content, structure, baseMatch.end, ["call", "apply"]); if (operation) { const openParenIndex = skipWhitespaceForward(structure, operation.end); if (structure[openParenIndex] !== "(") continue; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ index: match.index, mode: operation.value, openParenIndex, closeParenIndex }); continue; } const openParenIndex = skipWhitespaceForward(structure, baseMatch.end); if (structure[openParenIndex] !== "(") continue; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ index: match.index, mode: "direct", openParenIndex, closeParenIndex }); } const aliasNames = [...new Set(aliases.filter((alias) => alias.kind === "reflectApply").map((alias) => alias.name))]; if (aliasNames.length === 0) return matches; const secondOrderAliasNames = [...new Set(aliases.filter((alias) => alias.kind === "reflectApply" && !alias.bound).map((alias) => alias.name))]; const aliasSecondOrderPattern = secondOrderAliasNames.length > 0 ? new RegExp(String.raw`\b(${secondOrderAliasNames.map(escapeRegExp).join("|")})\b\s*(?:\.|\?\.)\s*(call|apply)\s*\(`, "g") : null; for (const match of aliasDirectInvocationCallMatches(structure, aliasNames)) { const { aliasName, aliasIndex, openParenIndex, closeParenIndex } = match; const aliasKind = staticBuiltinInvokerAliasKindAt(structure, aliases, aliasName, aliasIndex); if (aliasKind !== "reflectApply") continue; matches.push({ index: aliasIndex, mode: "direct", openParenIndex, closeParenIndex }); } if (aliasSecondOrderPattern) { const aliasSecondOrderNamePattern = new RegExp(String.raw`\b(${secondOrderAliasNames.map(escapeRegExp).join("|")})\b`, "g"); for (const match of structure.matchAll(aliasSecondOrderNamePattern)) { if (typeof match.index !== "number") continue; const aliasName = match[1]; const aliasIndex = match.index + match[0].indexOf(aliasName); if (isMemberIdentifierReference(structure, aliasIndex)) continue; const aliasKind = staticBuiltinInvokerAliasKindAt(structure, aliases, aliasName, aliasIndex); if (aliasKind !== "reflectApply") continue; const operation = staticBuiltinMemberOperationMatchAt(content, structure, aliasIndex + aliasName.length, ["call", "apply"]); if (!operation) continue; const openParenIndex = skipWhitespaceForward(structure, operation.end); if (structure[openParenIndex] !== "(") continue; const closeParenIndex = openParenIndex === -1 ? -1 : findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; matches.push({ index: aliasIndex, mode: operation.value, openParenIndex, closeParenIndex }); } } return matches.toSorted((left, right) => left.index - right.index); } function reflectApplyEquivalentArgumentSource(content, structure, invocation, argumentIndex) { if (invocation.mode === "direct") { return nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, argumentIndex); } if (invocation.mode === "call") { return nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, argumentIndex + 1); } if (invocation.mode === "apply") { const arrayArgument = nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, 1); return nthArrayElementSource(content, structure, arrayArgument, argumentIndex); } return null; } function prismaModelMethodCallPattern(models, methods) { return new RegExp( `${staticPrismaPropertyAccessPattern(models)}\\s*${staticPrismaPropertyAccessPattern(methods)}\\s*(?:\\?\\.)?\\s*\\(`, "g" ); } function findStaticPrismaDelegateAliases(content, structure, models) { const aliases = []; const declarationPattern = new RegExp( String.raw`\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=`, "g" ); for (const match of structure.matchAll(declarationPattern)) { if (typeof match.index !== "number") continue; const equalsIndex = structure.indexOf("=", match.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const branchDelegateMatch = staticInitializerBranchMatch( content, structure, initializerStart, statementEnd, (scanContent, scanStructure, sourceStart, sourceEnd) => completeStaticPrismaDelegateAliasExpressionMatch(scanContent, scanStructure, sourceStart, sourceEnd, models) ); if (branchDelegateMatch?.branch) { // 条件/逻辑分支里只要出现受保护 Prisma delegate,后续 alias.method(...) 必须按真实 delegate 写入扫描。 aliases.push({ name: match[1], index: match.index, end: statementEnd }); continue; } const delegateMatch = staticPrismaDelegateExpressionMatchAt(content, structure, initializerStart, models); if (!delegateMatch) continue; const nextIndex = skipWhitespaceForward(structure, delegateMatch.end); // 只接受 `const jobs = db.job;` 这类完整 delegate alias,避免把 `db.job.update` 误判成 alias。 if (structure[nextIndex] === "." || structure[nextIndex] === "[" || structure[nextIndex] === "(" || structure[nextIndex] === "?") { continue; } const aliasEnd = simpleStaticAliasInitializerEnd(structure, delegateMatch.end, statementEnd); if (aliasEnd === -1) continue; aliases.push({ name: match[1], index: match.index, end: aliasEnd }); } aliases.push(...findStaticPrismaDestructuredDelegateAliases(content, structure, models)); aliases.push(...findStaticPrismaAssignedDestructuredDelegateAliases(content, structure, models)); aliases.push(...findStaticPrismaAssignedDelegateAliases(content, structure, models)); const directAliases = propagateStaticIdentifierAliases(structure, aliases, (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end })); const wrappers = prismaDelegateWrapperSources(content, structure, models, directAliases); directAliases.push(...findStaticWrapperExtractionAliases(content, structure, wrappers)); return propagateStaticIdentifierAliases(structure, directAliases, (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end })); } function findStaticPrismaDestructuredDelegateAliases(content, structure, models) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s*\{/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const openBraceIndex = structure.indexOf("{", declarationMatch.index); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) continue; const names = destructuredAliasNamesForStaticKeys(content, structure, openBraceIndex + 1, closeBraceIndex, models, declarationMatch.index); if (names.length === 0) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] === ":") { const equalsAfterType = structure.indexOf("=", cursor + 1); const semicolonAfterType = structure.indexOf(";", cursor + 1); if (equalsAfterType === -1 || (semicolonAfterType !== -1 && semicolonAfterType < equalsAfterType)) continue; cursor = skipWhitespaceForward(structure, equalsAfterType + 1); } else { if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); } const receiverMatch = simpleStaticMemberChainMatchAt(content, structure, cursor); if (!receiverMatch) continue; // 解构 alias 只跟踪静态 Prisma client receiver,避免把运行时对象结构误判成 delegate。 const expressionEnd = cursor + receiverMatch[0].length; const statementEnd = findStatementEnd(structure, cursor); const aliasEnd = simpleStaticAliasInitializerEnd(structure, expressionEnd, statementEnd); if (aliasEnd === -1) continue; for (const name of names) { aliases.push({ name, index: declarationMatch.index, end: aliasEnd }); } } return aliases; } function findStaticPrismaAssignedDestructuredDelegateAliases(content, structure, models) { const aliases = []; const assignmentPattern = /\(\s*\{/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const openParenIndex = structure.indexOf("(", assignmentMatch.index); if (openParenIndex === -1 || !isParenthesizedExpressionOpen(structure, openParenIndex)) continue; const openBraceIndex = structure.indexOf("{", openParenIndex); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeBraceIndex === -1 || closeParenIndex === -1 || closeBraceIndex > closeParenIndex) continue; const names = destructuredAliasNamesForStaticKeys(content, structure, openBraceIndex + 1, closeBraceIndex, models, assignmentMatch.index); if (names.length === 0) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); const receiverMatch = simpleStaticMemberChainMatchAt(content, structure, cursor); if (!receiverMatch) continue; const expressionEnd = cursor + receiverMatch[0].length; if (skipWhitespaceForward(structure, expressionEnd) !== closeParenIndex) continue; // 赋值式解构 alias 覆盖 `let versions; ({ gameVersion: versions } = db)`。 const statementEnd = findStatementEnd(structure, openParenIndex); const aliasEnd = simpleStaticAliasInitializerEnd(structure, closeParenIndex + 1, statementEnd); if (aliasEnd === -1) continue; for (const name of names) { aliases.push({ name, index: assignmentMatch.index, end: aliasEnd }); } } return aliases; } function isStaticAliasExpressionContinuation(structure, index) { if (index >= structure.length) return false; if (structure.startsWith("?.", index)) return true; return "([.`+-/*%,?:&|<>=!".includes(structure[index]); } function simpleStaticAliasInitializerEnd(structure, expressionEnd, statementEnd) { const nextTokenIndex = skipWhitespaceForward(structure, expressionEnd); if (nextTokenIndex === statementEnd) return statementEnd; const gapAfterExpression = structure.slice(expressionEnd, nextTokenIndex); if (!hasLineTerminator(gapAfterExpression)) return -1; if (isStaticAliasExpressionContinuation(structure, nextTokenIndex)) return -1; return expressionEnd; } function completeStaticPrismaDelegateAliasExpressionMatch(content, structure, sourceStart, sourceEnd, models) { const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd); const expressionStart = skipWhitespaceForward(structure, unwrapped.start); const expressionEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (expressionStart >= expressionEnd) return null; const delegateMatch = staticPrismaDelegateExpressionMatchAt(content, structure, expressionStart, models); if (!delegateMatch || delegateMatch.end > expressionEnd) return null; if (!isAtStaticWrapperExpressionEnd(structure, delegateMatch.end, expressionEnd)) return null; return { index: expressionStart, end: expressionEnd }; } function findStaticPrismaAssignedDelegateAliases(content, structure, models) { const aliases = []; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; if (isMemberIdentifierReference(structure, assignmentMatch.index)) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const branchDelegateMatch = staticInitializerBranchMatch( content, structure, initializerStart, statementEnd, (scanContent, scanStructure, sourceStart, sourceEnd) => completeStaticPrismaDelegateAliasExpressionMatch(scanContent, scanStructure, sourceStart, sourceEnd, models) ); if (branchDelegateMatch?.branch) { // 后续赋值同样按 fail-closed 处理 mixed ternary/logical delegate source。 aliases.push({ name: assignmentMatch[1], index: assignmentMatch.index, end: statementEnd }); continue; } const delegateMatch = staticPrismaDelegateExpressionMatchAt(content, structure, initializerStart, models); if (!delegateMatch) continue; if (delegateMatch.end > statementEnd) continue; const nextIndex = skipWhitespaceForward(structure, delegateMatch.end); if (structure[nextIndex] === "." || structure[nextIndex] === "[" || structure[nextIndex] === "(" || structure[nextIndex] === "?") { continue; } const aliasEnd = simpleStaticAliasInitializerEnd(structure, delegateMatch.end, statementEnd); if (aliasEnd === -1) continue; // 后置赋值 alias 只接受 `jobs = db.job` 这种完整 delegate,避免比较、箭头或复杂表达式误判。 aliases.push({ name: assignmentMatch[1], index: assignmentMatch.index, end: aliasEnd }); } return aliases; } function prismaDelegateSourceMatcher(models, aliasResolver = null) { return (content, structure, sourceStart, sourceEnd) => { const delegateMatch = staticPrismaDelegateExpressionMatchAt(content, structure, sourceStart, models); if (delegateMatch && isAtStaticWrapperExpressionEnd(structure, delegateMatch.end, sourceEnd)) return delegateMatch; return aliasResolver ? aliasResolver(structure, sourceStart, sourceEnd) : null; }; } function prismaDelegateWrapperSources(content, structure, models, aliases = []) { const aliasResolver = (scanStructure, sourceStart, sourceEnd) => staticIdentifierAliasSourceMatchAt(scanStructure, aliases, sourceStart, sourceEnd, sourceStart); return staticWrapperSources(content, structure, prismaDelegateSourceMatcher(models, aliasResolver)); } function prismaModelMethodSourceMatcher(models, methods, aliases, methodAliases = []) { return (content, structure, sourceStart, sourceEnd) => { const methodAccess = staticPrismaModelMethodAccessMatchAt(content, structure, sourceStart, models, methods, aliases); if (methodAccess) { if (isAtStaticWrapperExpressionEnd(structure, methodAccess.end, sourceEnd)) return methodAccess; const bindCloseParenIndex = prismaMethodBindCloseParenIndex(content, structure, methodAccess.end, sourceEnd); // 函数返回或 Object.assign wrapper 里出现 `db.model.method.bind(db.model)` 时,后续调用仍等价于直接 Prisma 写。 if (bindCloseParenIndex !== -1 && isAtStaticWrapperExpressionEnd(structure, bindCloseParenIndex + 1, sourceEnd)) { return { index: methodAccess.index, end: bindCloseParenIndex + 1 }; } } return staticIdentifierAliasSourceMatchAt(structure, methodAliases, sourceStart, sourceEnd, sourceStart); }; } function prismaModelMethodWrapperSources(content, structure, models, methods, aliases, methodAliases = []) { return staticWrapperSources(content, structure, prismaModelMethodSourceMatcher(models, methods, aliases, methodAliases)); } function prismaModelMethodWrapperAliases(content, structure, methodWrappers) { return propagateStaticIdentifierAliases( structure, findStaticWrapperExtractionAliases(content, structure, methodWrappers), (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end }) ); } function staticPrismaDelegateExpressionOrAliasMatchAt(content, structure, index, models, aliases = []) { const delegateMatch = staticPrismaDelegateExpressionMatchAt(content, structure, index, models); if (delegateMatch) return delegateMatch; const aliasMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(index)); if (!aliasMatch || isMemberIdentifierReference(structure, index)) return null; const aliasName = aliasMatch[0]; if (!latestStaticAliasRecordAt(structure, aliases, aliasName, index)) return null; return { index, end: index + aliasName.length }; } function findStaticPrismaDestructuredModelMethodAliases(content, structure, models, methods, aliases) { const methodAliases = []; const declarationPattern = /\b(?:const|let|var)\s*\{/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const openBraceIndex = structure.indexOf("{", declarationMatch.index); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) continue; const names = destructuredAliasNamesForStaticKeys(content, structure, openBraceIndex + 1, closeBraceIndex, methods, declarationMatch.index); if (names.length === 0) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] === ":") { const equalsAfterType = structure.indexOf("=", cursor + 1); const semicolonAfterType = structure.indexOf(";", cursor + 1); if (equalsAfterType === -1 || (semicolonAfterType !== -1 && semicolonAfterType < equalsAfterType)) continue; cursor = skipWhitespaceForward(structure, equalsAfterType + 1); } else { if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); } const delegateMatch = staticPrismaDelegateExpressionOrAliasMatchAt(content, structure, cursor, models, aliases); if (!delegateMatch) continue; const statementEnd = findStatementEnd(structure, cursor); const aliasEnd = simpleStaticAliasInitializerEnd(structure, delegateMatch.end, statementEnd); if (aliasEnd === -1) continue; // Prisma delegate method destructuring,例如 `const { delete: del } = db.auditLog`。 for (const name of names) { methodAliases.push({ name, index: declarationMatch.index, end: aliasEnd }); } } return methodAliases; } function findStaticPrismaAssignedDestructuredModelMethodAliases(content, structure, models, methods, aliases) { const methodAliases = []; const assignmentPattern = /\(\s*\{/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const openParenIndex = structure.indexOf("(", assignmentMatch.index); if (openParenIndex === -1 || !isParenthesizedExpressionOpen(structure, openParenIndex)) continue; const openBraceIndex = structure.indexOf("{", openParenIndex); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeBraceIndex === -1 || closeParenIndex === -1 || closeBraceIndex > closeParenIndex) continue; const names = destructuredAliasNamesForStaticKeys(content, structure, openBraceIndex + 1, closeBraceIndex, methods, assignmentMatch.index); if (names.length === 0) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); const delegateMatch = staticPrismaDelegateExpressionOrAliasMatchAt(content, structure, cursor, models, aliases); if (!delegateMatch || skipWhitespaceForward(structure, delegateMatch.end) !== closeParenIndex) continue; const statementEnd = findStatementEnd(structure, openParenIndex); const aliasEnd = simpleStaticAliasInitializerEnd(structure, closeParenIndex + 1, statementEnd); if (aliasEnd === -1) continue; // 赋值式 method destructuring,例如 `let del; ({ delete: del } = db.auditLog)`。 for (const name of names) { methodAliases.push({ name, index: assignmentMatch.index, end: aliasEnd }); } } return methodAliases; } function findStaticPrismaModelMethodBoundAliases(content, structure, models, methods, aliases, methodWrappers = [], methodAliases = []) { const boundAliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const bindAliasEnd = staticPrismaModelMethodBoundAliasInitializerEnd( content, structure, initializerStart, statementEnd, models, methods, aliases, methodWrappers, methodAliases ); if (bindAliasEnd === -1) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, bindAliasEnd, statementEnd); if (aliasEnd === -1) continue; // Prisma method `.bind(delegate)` alias 后续等价于直接 method invocation。 boundAliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: aliasEnd }); } for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const variableName = assignmentMatch[1]; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; if (/\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, assignmentMatch.index - 16), variableIndex))) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const bindAliasEnd = staticPrismaModelMethodBoundAliasInitializerEnd( content, structure, initializerStart, statementEnd, models, methods, aliases, methodWrappers, methodAliases ); if (bindAliasEnd === -1) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, bindAliasEnd, statementEnd); if (aliasEnd === -1) continue; // 后续赋值 alias 覆盖 `let update; update = db.model.method.bind(db.model)`,保持和声明式 bound alias 同一语义。 boundAliases.push({ name: variableName, index: assignmentMatch.index, end: aliasEnd }); } return boundAliases; } function staticPrismaModelMethodBoundAliasInitializerEnd( content, structure, sourceStart, sourceEnd, models, methods, aliases, methodWrappers, methodAliases ) { const completeMatch = completeStaticPrismaModelMethodBoundAliasExpressionMatch( content, structure, sourceStart, sourceEnd, models, methods, aliases, methodWrappers, methodAliases ); if (completeMatch) return completeMatch.end; const branchMatches = staticExpressionBranchSources(structure, sourceStart, sourceEnd) .map((branch) => completeStaticPrismaModelMethodBoundAliasExpressionMatch( content, structure, branch.index, branch.end, models, methods, aliases, methodWrappers, methodAliases ) ) .filter(Boolean); // 条件/逻辑表达式中任一静态分支是 guarded/audit/job method bind,后续 callable alias 按违规路径扫描。 return branchMatches.length > 0 ? sourceEnd : -1; } function completeStaticPrismaModelMethodBoundAliasExpressionMatch( content, structure, sourceStart, sourceEnd, models, methods, aliases, methodWrappers, methodAliases ) { const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd); const expressionStart = skipWhitespaceForward(structure, unwrapped.start); const expressionEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (expressionStart >= expressionEnd) return null; const methodAccess = staticPrismaModelMethodAccessMatchAt( content, structure, expressionStart, models, methods, aliases, methodWrappers, methodAliases ); if (!methodAccess || methodAccess.end > expressionEnd) return null; const bindCloseParenIndex = prismaMethodBindCloseParenIndex(content, structure, methodAccess.end, expressionEnd); if (bindCloseParenIndex === -1) return null; // bind helper 返回的是右括号下标;这里用 exclusive end 严格校验,避免越过 ternary 分支边界的空白继续扫到 `:`。 const bindExpressionEnd = bindCloseParenIndex + 1; if (bindExpressionEnd !== expressionEnd) return null; return { index: expressionStart, end: expressionEnd }; } function completeStaticPrismaModelMethodPlainAliasExpressionMatch( content, structure, sourceStart, sourceEnd, models, methods, aliases, methodWrappers, methodAliases ) { const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd); const expressionStart = skipWhitespaceForward(structure, unwrapped.start); const expressionEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (expressionStart >= expressionEnd) return null; const methodAccess = staticPrismaModelMethodAccessMatchAt( content, structure, expressionStart, models, methods, aliases, methodWrappers, methodAliases ); if (!methodAccess || methodAccess.end > expressionEnd) return null; if (!isAtStaticWrapperExpressionEnd(structure, methodAccess.end, expressionEnd)) return null; return { index: expressionStart, end: expressionEnd }; } function staticPrismaModelMethodPlainAliasInitializerEnd( content, structure, sourceStart, sourceEnd, models, methods, aliases, methodWrappers, methodAliases ) { const plainMatch = staticInitializerBranchMatch( content, structure, sourceStart, sourceEnd, (scanContent, scanStructure, branchStart, branchEnd) => completeStaticPrismaModelMethodPlainAliasExpressionMatch( scanContent, scanStructure, branchStart, branchEnd, models, methods, aliases, methodWrappers, methodAliases ) ); // 条件/逻辑表达式中任一静态分支是 Prisma plain method,后续 callable alias 仍按 Prisma 写入扫描。 return plainMatch ? (plainMatch.branch ? sourceEnd : plainMatch.end) : -1; } function findStaticPrismaModelMethodPlainAliases(content, structure, models, methods, aliases, methodWrappers = [], methodAliases = []) { const plainAliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const plainAliasEnd = staticPrismaModelMethodPlainAliasInitializerEnd( content, structure, initializerStart, statementEnd, models, methods, aliases, methodWrappers, methodAliases ); if (plainAliasEnd === -1) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, plainAliasEnd, statementEnd); if (aliasEnd === -1) continue; // `const del = db.job.delete` 这类 plain method alias 后续通过 direct/call/apply/bind 都等价于 Prisma method invocation。 plainAliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: aliasEnd }); } for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const variableName = assignmentMatch[1]; const variableIndex = assignmentMatch.index + assignmentMatch[0].indexOf(variableName); if (isMemberIdentifierReference(structure, variableIndex)) continue; if (/\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, assignmentMatch.index - 16), variableIndex))) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const plainAliasEnd = staticPrismaModelMethodPlainAliasInitializerEnd( content, structure, initializerStart, statementEnd, models, methods, aliases, methodWrappers, methodAliases ); if (plainAliasEnd === -1) continue; const aliasEnd = simpleStaticAliasInitializerEnd(structure, plainAliasEnd, statementEnd); if (aliasEnd === -1) continue; // 后续赋值 alias 覆盖 `let update; update = db.gameVersion.update`,保持和声明式 plain alias 同一语义。 plainAliases.push({ name: variableName, index: assignmentMatch.index, end: aliasEnd }); } return plainAliases; } function staticExpressionBranchSources(structure, sourceStart, sourceEnd) { const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd); const expressionStart = skipWhitespaceForward(structure, unwrapped.start); const expressionEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (expressionStart >= expressionEnd) return []; const ternary = topLevelTernaryExpressionParts(structure, expressionStart, expressionEnd); if (ternary) return [ternary.whenTrue, ternary.whenFalse]; const logical = topLevelLogicalExpressionParts(structure, expressionStart, expressionEnd); return logical ?? []; } function staticInitializerBranchMatch(content, structure, sourceStart, sourceEnd, matcher) { const completeMatch = matcher(content, structure, sourceStart, sourceEnd); if (completeMatch) return completeMatch; for (const branch of staticExpressionBranchSources(structure, sourceStart, sourceEnd)) { // 分支表达式递归展开,确保 `a ? safe : (b || db.model.method)` 这类嵌套静态分支不会 fail-open。 const branchMatch = staticInitializerBranchMatch(content, structure, branch.index, branch.end, matcher); if (branchMatch) return { ...branchMatch, branch }; } return null; } function topLevelTernaryExpressionParts(structure, sourceStart, sourceEnd) { let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; let questionIndex = -1; for (let cursor = sourceStart; cursor < sourceEnd; cursor += 1) { const char = structure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (parenDepth !== 0 || braceDepth !== 0 || bracketDepth !== 0) continue; if (char === "?" && structure[cursor + 1] !== ".") { questionIndex = cursor; break; } } if (questionIndex === -1) return null; parenDepth = 0; braceDepth = 0; bracketDepth = 0; let nestedQuestionDepth = 0; for (let cursor = questionIndex + 1; cursor < sourceEnd; cursor += 1) { const char = structure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (parenDepth !== 0 || braceDepth !== 0 || bracketDepth !== 0) continue; if (char === "?" && structure[cursor + 1] !== ".") { nestedQuestionDepth += 1; continue; } if (char !== ":") continue; if (nestedQuestionDepth > 0) { nestedQuestionDepth -= 1; continue; } return { whenTrue: { index: skipWhitespaceForward(structure, questionIndex + 1), end: skipWhitespaceBackward(structure, cursor - 1) + 1 }, whenFalse: { index: skipWhitespaceForward(structure, cursor + 1), end: sourceEnd } }; } return null; } function topLevelLogicalExpressionParts(structure, sourceStart, sourceEnd) { let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; for (let cursor = sourceStart; cursor < sourceEnd - 1; cursor += 1) { const char = structure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (parenDepth !== 0 || braceDepth !== 0 || bracketDepth !== 0) continue; const operator = structure.slice(cursor, cursor + 2); if (operator !== "||" && operator !== "&&") continue; return [ { index: sourceStart, end: skipWhitespaceBackward(structure, cursor - 1) + 1 }, { index: skipWhitespaceForward(structure, cursor + 2), end: sourceEnd } ]; } return null; } function prismaMethodBindCloseParenIndex(content, structure, methodAccessEnd, statementEnd) { const operation = staticCallableMemberOperationMatchAt(content, structure, methodAccessEnd, ["bind"]); if (!operation) return -1; if (operation.closeParenIndex === -1 || operation.closeParenIndex > statementEnd) return -1; return operation.closeParenIndex; } function prismaDelegateAliasMethodCallPattern(aliasNames, methods) { const aliases = aliasNames.map(escapeRegExp).join("|"); return new RegExp(String.raw`\b(${aliases})\b\s*${staticPrismaPropertyAccessPattern(methods)}\s*(?:\?\.)?\s*\(`, "g"); } function staticPrismaModelMethodAccessMatchAt(content, structure, index, models, methods, aliases = [], methodWrappers = [], methodAliases = []) { const accessStart = skipWhitespaceForward(structure, index); const receiverMatch = /^(?:[A-Za-z_$][\w$]*|this)\b/.exec(structure.slice(accessStart)); if (receiverMatch) { const modelAccessStart = skipWhitespaceForward(structure, accessStart + receiverMatch[0].length); const modelAccess = staticPrismaPropertyAccessMatchAt(content, structure, modelAccessStart, models, modelAccessStart); if (modelAccess) { const methodAccessStart = skipWhitespaceForward(structure, modelAccess.end); const methodAccess = staticPrismaPropertyAccessMatchAt(content, structure, methodAccessStart, methods, methodAccessStart); if (methodAccess) return { index: modelAccess.index, end: methodAccess.end }; } } const wrapperUsage = staticWrapperSourceUsageMatchAt(content, structure, methodWrappers, accessStart, accessStart); if (wrapperUsage) return { index: wrapperUsage.index, end: wrapperUsage.end }; const methodAliasMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(accessStart)); if (methodAliasMatch && !isMemberIdentifierReference(structure, accessStart)) { const aliasName = methodAliasMatch[0]; if (latestStaticAliasRecordAt(structure, methodAliases, aliasName, accessStart)) { return { index: accessStart, end: accessStart + aliasName.length }; } } const aliasMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(accessStart)); if (!aliasMatch || aliases.length === 0) return null; const aliasName = aliasMatch[0]; if (!latestStaticAliasRecordAt(structure, aliases, aliasName, accessStart)) return null; if (isMemberIdentifierReference(structure, accessStart)) return null; const methodAccessStart = skipWhitespaceForward(structure, accessStart + aliasName.length); const methodAccess = staticPrismaPropertyAccessMatchAt(content, structure, methodAccessStart, methods, methodAccessStart); return methodAccess ? { index: accessStart, end: methodAccess.end } : null; } function staticPrismaModelMethodAccessArgumentMatch(content, structure, argument, models, methods, aliases, methodWrappers = [], methodAliases = []) { if (!argument) return null; const targetStart = skipWhitespaceForward(structure, argument.index); const targetEnd = skipWhitespaceBackward(structure, argument.end - 1) + 1; if (targetStart >= targetEnd) return null; const accessMatch = staticPrismaModelMethodAccessMatchAt( content, structure, targetStart, models, methods, aliases, methodWrappers, methodAliases ); if (!accessMatch) return null; return skipWhitespaceForward(structure, accessMatch.end) === targetEnd ? accessMatch : null; } function prismaMethodEquivalentInvocationMatch(content, structure, methodAccessEnd) { const callStart = hasOptionalCallOperatorAt(structure, methodAccessEnd); if (structure[callStart] === "(") return { callStart }; const operation = staticCallableMemberOperationMatchAt(content, structure, methodAccessEnd, ["call", "apply", "bind"]); if (!operation) return null; if (operation.kind === "call" || operation.kind === "apply") { return { callStart: operation.openParenIndex }; } let boundCallStart = skipWhitespaceForward(structure, operation.closeParenIndex + 1); if (structure.startsWith("?.", boundCallStart)) { boundCallStart = skipWhitespaceForward(structure, boundCallStart + 2); } if (structure[boundCallStart] !== "(") return null; const boundCallEnd = findMatchingParen(structure, boundCallStart); return boundCallEnd === -1 ? null : { callStart: boundCallStart }; } function prismaFunctionPrototypeMethodCallMatchesInStructure(content, structure, models, methods, aliases, builtinInvokerAliases, methodWrappers, methodAliases) { const matches = []; const boundTargetAliases = findFunctionPrototypeBoundTargetAliases(content, structure); for (const invocation of functionPrototypeCallInvocationMatches(content, structure, builtinInvokerAliases)) { const targetArgument = functionPrototypeEquivalentArgumentSource(content, structure, invocation, 0); const targetAccess = staticPrismaModelMethodAccessArgumentMatch(content, structure, targetArgument, models, methods, aliases, methodWrappers, methodAliases); if (!targetAccess) continue; // Function.prototype.call/apply 的目标函数在参数位;命中后复用外层调用体做 Prisma 写入扫描。 matches.push({ index: targetAccess.index, callStart: invocation.openParenIndex }); } for (const invocation of reflectApplyInvocationMatches(content, structure, builtinInvokerAliases)) { const reflectTargetArgument = reflectApplyEquivalentArgumentSource(content, structure, invocation, 0); if (!functionPrototypeMethodKindFromArgument(content, structure, reflectTargetArgument, builtinInvokerAliases)) { const reflectTargetAccess = staticPrismaModelMethodAccessArgumentMatch( content, structure, reflectTargetArgument, models, methods, aliases, methodWrappers, methodAliases ); if (reflectTargetAccess) { // Reflect.apply(method, receiver, [args]) 等价执行 Prisma method target。 matches.push({ index: reflectTargetAccess.index, callStart: invocation.openParenIndex }); } continue; } const targetArgument = reflectApplyEquivalentArgumentSource(content, structure, invocation, 1); const targetAccess = staticPrismaModelMethodAccessArgumentMatch(content, structure, targetArgument, models, methods, aliases, methodWrappers, methodAliases); if (!targetAccess) continue; matches.push({ index: targetAccess.index, callStart: invocation.openParenIndex }); } for (const invocation of functionPrototypeBoundTargetInvocationMatches(content, structure, boundTargetAliases)) { const targetAccess = staticPrismaModelMethodAccessArgumentMatch( content, structure, invocation.targetArgument, models, methods, aliases, methodWrappers, methodAliases ); if (!targetAccess) continue; // Function.prototype.call/apply.bind(method)(...) 绑定的是真实 Prisma method target。 matches.push({ index: targetAccess.index, callStart: invocation.openParenIndex }); } return matches; } function prismaModelMethodCallMatchesInStructure(content, structure, models, methods) { const scanStructure = stripParenthesesAroundStaticMemberChains(content, structure); const matches = []; for (const modelAccess of staticPrismaPropertyAccessMatches(content, scanStructure, models)) { const methodAccessStart = skipWhitespaceForward(scanStructure, modelAccess.end); const methodAccess = staticPrismaPropertyAccessMatchAt(content, scanStructure, methodAccessStart, methods, methodAccessStart); if (!methodAccess) continue; const invocation = prismaMethodEquivalentInvocationMatch(content, scanStructure, methodAccess.end); if (invocation) matches.push({ index: modelAccess.index, callStart: invocation.callStart }); } const aliases = findStaticPrismaDelegateAliases(content, scanStructure, models); const builtinInvokerAliases = findStaticBuiltinInvokerAliases(content, scanStructure); const delegateWrappers = prismaDelegateWrapperSources(content, scanStructure, models, aliases); const initialMethodWrappers = prismaModelMethodWrapperSources(content, scanStructure, models, methods, aliases); const initialMethodAliases = prismaModelMethodWrapperAliases(content, scanStructure, initialMethodWrappers) .concat(findStaticPrismaDestructuredModelMethodAliases(content, scanStructure, models, methods, aliases)) .concat(findStaticPrismaAssignedDestructuredModelMethodAliases(content, scanStructure, models, methods, aliases)) .concat(findStaticPrismaModelMethodPlainAliases(content, scanStructure, models, methods, aliases, initialMethodWrappers)); const boundMethodAliases = findStaticPrismaModelMethodBoundAliases( content, scanStructure, models, methods, aliases, initialMethodWrappers, initialMethodAliases ); const methodAliases = propagateStaticIdentifierAliases( scanStructure, initialMethodAliases.concat(boundMethodAliases), (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end }) ); const methodWrappers = prismaModelMethodWrapperSources(content, scanStructure, models, methods, aliases, methodAliases); methodAliases.push(...prismaModelMethodWrapperAliases(content, scanStructure, methodWrappers)); matches.push( ...prismaFunctionPrototypeMethodCallMatchesInStructure( content, scanStructure, models, methods, aliases, builtinInvokerAliases, methodWrappers, methodAliases ) ); for (const delegateWrapperUsage of staticWrapperSourceUsageMatches(content, scanStructure, delegateWrappers)) { const methodAccessStart = skipWhitespaceForward(scanStructure, delegateWrapperUsage.end); const methodAccess = staticPrismaPropertyAccessMatchAt(content, scanStructure, methodAccessStart, methods, methodAccessStart); if (!methodAccess) continue; const invocation = prismaMethodEquivalentInvocationMatch(content, scanStructure, methodAccess.end); if (invocation) matches.push({ index: delegateWrapperUsage.index, callStart: invocation.callStart }); } for (const methodWrapperUsage of staticWrapperSourceUsageMatches(content, scanStructure, methodWrappers)) { const invocation = prismaMethodEquivalentInvocationMatch(content, scanStructure, methodWrapperUsage.end); if (invocation) matches.push({ index: methodWrapperUsage.index, callStart: invocation.callStart }); } const methodAliasNames = [...new Set(methodAliases.map((alias) => alias.name))]; const methodAliasPattern = methodAliasNames.length > 0 ? new RegExp(String.raw`\b(${methodAliasNames.map(escapeRegExp).join("|")})\b`, "g") : null; if (methodAliasPattern) { for (const match of scanStructure.matchAll(methodAliasPattern)) { if (typeof match.index !== "number") continue; const aliasName = match[1]; if (!latestStaticAliasRecordAt(scanStructure, methodAliases, aliasName, match.index)) continue; if (isMemberIdentifierReference(scanStructure, match.index)) continue; const invocation = prismaMethodEquivalentInvocationMatch(content, scanStructure, match.index + aliasName.length); if (invocation) matches.push({ index: match.index, callStart: invocation.callStart }); } } const aliasNames = [...new Set(aliases.map((alias) => alias.name))]; const aliasPattern = aliasNames.length > 0 ? new RegExp(String.raw`\b(${aliasNames.map(escapeRegExp).join("|")})\b`, "g") : null; if (aliasPattern) { for (const match of scanStructure.matchAll(aliasPattern)) { if (typeof match.index !== "number") continue; const aliasName = match[1]; if (!latestStaticAliasRecordAt(scanStructure, aliases, aliasName, match.index)) continue; if (isMemberIdentifierReference(scanStructure, match.index)) continue; const methodAccessStart = skipWhitespaceForward(scanStructure, match.index + aliasName.length); const methodAccess = staticPrismaPropertyAccessMatchAt(content, scanStructure, methodAccessStart, methods, methodAccessStart); if (!methodAccess) continue; const invocation = prismaMethodEquivalentInvocationMatch(content, scanStructure, methodAccess.end); if (invocation) matches.push({ index: match.index, callStart: invocation.callStart }); } } return matches.toSorted((left, right) => left.index - right.index); } function prismaModelMethodCallMatches(content, models, methods) { const structure = stripStringLiteralsExceptPropertyKeys(maskCommentsOutsideStringLiterals(content)); return prismaModelMethodCallMatchesInStructure(content, structure, models, methods); } function sqlTableListContainingPattern(targetTableReference) { const tableReference = sqlAnyTableReferencePattern(); const tableListItem = String.raw`(?:ONLY\s+)?${tableReference}(?:\s*\*)?`; const targetTableListItem = String.raw`(?:ONLY\s+)?${targetTableReference}\b(?:\s*\*)?`; // 表列表允许目标表出现在任意位置,避免只匹配列表首尾造成绕过。 return String.raw`(?:${tableListItem}\s*,\s*)*${targetTableListItem}(?:\s*,\s*${tableListItem})*`; } function sqlTruncateTableListPattern(targetTableReference) { return String.raw`\bTRUNCATE(?:\s+TABLE)?\s+${sqlTableListContainingPattern(targetTableReference)}`; } function sqlDropTableListPattern(targetTableReference) { return String.raw`\bDROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?${sqlTableListContainingPattern(targetTableReference)}`; } function stripStringLiterals(content) { return content .replace(/"(?:\\.|[^"\\])*"/gs, '""') .replace(/'(?:\\.|[^'\\])*'/gs, "''") .replace(/`(?:\\.|[^`\\])*`/gs, "``"); } function skipWhitespaceForward(content, index) { let cursor = index; while (cursor < content.length && /\s/.test(content[cursor])) cursor += 1; return cursor; } function skipWhitespaceBackward(content, index) { let cursor = index; while (cursor >= 0 && /\s/.test(content[cursor])) cursor -= 1; return cursor; } function isStaticQuotedPropertyKeyOrAccess(content, literalStart, literalEnd) { const staticComputedSpan = staticComputedStringPropertyAccessSpan(content, literalStart); if (staticComputedSpan) return true; const afterLiteral = skipWhitespaceForward(content, literalEnd); if (content[afterLiteral] === ":") return true; const afterComputedKey = skipWhitespaceForward(content, afterLiteral + 1); if (content[afterLiteral] !== "]") return false; const beforeLiteral = skipWhitespaceBackward(content, literalStart - 1); if (content[beforeLiteral] !== "[") return false; // 保留静态 bracket key,覆盖对象 key 与 Prisma property access 两种源码形态。 return ( content[afterComputedKey] === ":" || content[afterComputedKey] === "." || content[afterComputedKey] === "?" || content[afterComputedKey] === "[" || content[afterComputedKey] === "(" || content[afterComputedKey] === ";" || content[afterComputedKey] === "," || content[afterComputedKey] === ")" || content[afterComputedKey] === "}" || typeof content[afterComputedKey] === "undefined" ); } function staticComputedStringPropertyAccessSpan(content, literalStart) { const openBracketIndex = content.lastIndexOf("[", literalStart); if (openBracketIndex === -1) return null; if (content.lastIndexOf("]", literalStart) > openBracketIndex) return null; const structure = maskStringLiterals(maskCommentsOutsideStringLiterals(content)); const closeBracketIndex = findMatchingBracket(structure, openBracketIndex); if (closeBracketIndex === -1 || literalStart > closeBracketIndex) return null; const afterComputedKey = skipWhitespaceForward(content, closeBracketIndex + 1); if ( !( content[afterComputedKey] === ":" || content[afterComputedKey] === "." || content[afterComputedKey] === "?" || content[afterComputedKey] === "[" || content[afterComputedKey] === "(" || content[afterComputedKey] === ";" || content[afterComputedKey] === "," || content[afterComputedKey] === ")" || content[afterComputedKey] === "}" || typeof content[afterComputedKey] === "undefined" ) ) { return null; } const expressionStart = openBracketIndex + 1; const expressionEnd = closeBracketIndex; const values = staticJavaScriptExpressionValues( content, structure, content.slice(expressionStart, expressionEnd), expressionStart, openBracketIndex, new Set(), 0 ); return values.length === 1 ? { start: openBracketIndex, end: closeBracketIndex + 1 } : null; } function stripStringLiteralsExceptPropertyKeys(content) { let output = ""; for (let index = 0; index < content.length; index += 1) { const quote = content[index]; if (quote === "/" && isRegexLiteralStart(content, index)) { const literalEnd = findRegexLiteralEnd(content, index); if (literalEnd !== -1) { output += "/".padEnd(literalEnd - index, " ") + "/"; index = literalEnd; continue; } } if (quote !== '"' && quote !== "'" && quote !== "`") { output += quote; continue; } const literalStart = index; let literalEnd = literalStart + 1; while (literalEnd < content.length) { const char = content[literalEnd]; if (char === "\\") { literalEnd += 2; continue; } literalEnd += 1; if (char === quote) break; } const literal = content.slice(literalStart, literalEnd); // 只保留静态 key/access,普通字符串值仍等长剥离,避免破坏源码索引。 output += isStaticQuotedPropertyKeyOrAccess(content, literalStart, literalEnd) ? literal : `${quote}${" ".repeat(Math.max(0, literal.length - 2))}${quote}`; index = literalEnd - 1; } return output; } function maskCommentsOutsideStringLiterals(content) { let output = ""; let index = 0; while (index < content.length) { const char = content[index]; const next = content[index + 1]; if (char === "/" && next === "/") { output += " "; index += 2; while (index < content.length && content[index] !== "\n" && content[index] !== "\r") { output += " "; index += 1; } continue; } if (char === "/" && next === "*") { output += " "; index += 2; while (index < content.length) { const blockChar = content[index]; const blockNext = content[index + 1]; if (blockChar === "*" && blockNext === "/") { output += " "; index += 2; break; } output += blockChar === "\n" || blockChar === "\r" ? blockChar : " "; index += 1; } continue; } if (char === "/" && isRegexLiteralStart(content, index)) { const literalEnd = findRegexLiteralEnd(content, index); if (literalEnd !== -1) { output += content.slice(index, literalEnd + 1); index = literalEnd + 1; continue; } } if (char !== '"' && char !== "'" && char !== "`") { output += char; index += 1; continue; } output += char; index += 1; while (index < content.length) { const stringChar = content[index]; output += stringChar; index += 1; if (stringChar === "\\") { if (index < content.length) { output += content[index]; index += 1; } continue; } if (stringChar === char) break; } } return output; } function isRegexLiteralStart(content, slashIndex) { let cursor = skipWhitespaceBackward(content, slashIndex - 1); if (cursor < 0) return true; const previous = content[cursor]; if ("([{:;,=!?&|+-*%^~<>".includes(previous)) return true; return /\b(?:return|throw|case|delete|typeof|void|new|in|of|yield|await)\b/.test(content.slice(Math.max(0, cursor - 12), cursor + 1)); } function findRegexLiteralEnd(content, slashIndex) { let inCharClass = false; for (let cursor = slashIndex + 1; cursor < content.length; cursor += 1) { const char = content[cursor]; if (char === "\\") { cursor += 1; continue; } if (char === "[") inCharClass = true; if (char === "]") inCharClass = false; if (char === "/" && !inCharClass) { let end = cursor + 1; while (end < content.length && /[a-z]/i.test(content[end])) end += 1; return end - 1; } } return -1; } function findMatchingParen(content, openParenIndex) { let depth = 0; for (let index = openParenIndex; index < content.length; index += 1) { const char = content[index]; if (char === "(") depth += 1; if (char === ")") { depth -= 1; if (depth === 0) return index; } } return -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; if (char === "}") { depth -= 1; if (depth === 0) return index; } } return -1; } function findMatchingBracket(content, openBracketIndex) { let depth = 0; for (let index = openBracketIndex; index < content.length; index += 1) { const char = content[index]; if (char === "[") depth += 1; if (char === "]") { depth -= 1; if (depth === 0) return index; } } return -1; } function stripParenthesesAroundStaticMemberChains(content, structure) { let output = structure; let changed = false; do { changed = false; for (let index = 0; index < output.length; index += 1) { if (output[index] !== "(") continue; if (!isParenthesizedExpressionOpen(output, index)) continue; const closeParenIndex = findMatchingParen(output, index); if (closeParenIndex === -1) continue; const innerStart = skipWhitespaceForward(output, index + 1); const innerEnd = skipWhitespaceBackward(output, closeParenIndex - 1) + 1; if (innerStart >= innerEnd) continue; const memberMatch = simpleStaticMemberChainMatchAt(content, output, innerStart); if (!memberMatch || innerStart + memberMatch[0].length !== innerEnd) continue; // Prisma/raw callee 允许完整 member chain 外包多层括号;剥离括号后仍保留源码索引长度。 output = `${output.slice(0, index)} ${output.slice(index + 1, closeParenIndex)} ${output.slice(closeParenIndex + 1)}`; changed = true; break; } } while (changed); return output; } function maskStringLiterals(content) { let output = ""; let index = 0; while (index < content.length) { const quote = content[index]; if (quote === "/" && isRegexLiteralStart(content, index)) { const literalEnd = findRegexLiteralEnd(content, index); if (literalEnd !== -1) { output += "/".padEnd(literalEnd - index, " ") + "/"; index = literalEnd + 1; continue; } } if (quote !== '"' && quote !== "'" && quote !== "`") { output += quote; index += 1; continue; } output += quote; index += 1; while (index < content.length) { const char = content[index]; if (char === "\\") { output += " "; index += 1; if (index < content.length) { output += " "; index += 1; } continue; } if (char === quote) { output += quote; index += 1; break; } output += " "; index += 1; } } return output; } function findStringLiteralEnd(content, literalStart) { const quote = content[literalStart]; let cursor = literalStart + 1; while (cursor < content.length) { const char = content[cursor]; if (char === "\\") { cursor += 2; continue; } if (char === quote) return cursor; cursor += 1; } return -1; } function hasPrismaCallContaining(content, callPattern, termPattern) { // 结构匹配只看源码骨架;注释中的括号/花括号不能参与调用体边界计算。 const scanContent = stripStringLiteralsExceptPropertyKeys(maskCommentsOutsideStringLiterals(content)); for (const match of scanContent.matchAll(callPattern)) { const openParenIndex = scanContent.indexOf("(", match.index); if (openParenIndex === -1) continue; const closeParenIndex = findMatchingParen(scanContent, openParenIndex); if (closeParenIndex === -1) continue; const callBody = scanContent.slice(openParenIndex, closeParenIndex + 1); if (termPattern.test(callBody)) return true; } return false; } function hasPrismaCallBodyContaining(scanContent, callStart, termPattern) { const openParenIndex = scanContent[callStart] === "(" ? callStart : scanContent.indexOf("(", callStart); if (openParenIndex === -1) return false; const closeParenIndex = findMatchingParen(scanContent, openParenIndex); if (closeParenIndex === -1) return false; const callBody = scanContent.slice(openParenIndex, closeParenIndex + 1); return termPattern.test(callBody); } function hasPrismaModelMethodCallContaining(content, models, methods, termPattern) { // delegate alias 与直接 model access 共用同一调用体解析,避免 Job/Audit/guarded 边界分叉。 const scanContent = stripStringLiteralsExceptPropertyKeys(maskCommentsOutsideStringLiterals(content)); for (const match of prismaModelMethodCallMatchesInStructure(content, scanContent, models, methods)) { if (hasPrismaCallBodyContaining(scanContent, match.callStart, termPattern)) return true; } return false; } function hasVariableizedOrSpreadJobData(callBody) { return ( /\bdata\s*(?:[,}])/.test(callBody) || /\bdata\s*:\s*[A-Za-z_$][\w$]*\b/.test(callBody) || /\bdata\s*:\s*(?:\{|\[)[\s\S]*\.\.\./.test(callBody) ); } function hasComputedJobDataKey(callBody) { const dataObjectPattern = /\bdata\s*:\s*\{/g; for (const match of callBody.matchAll(dataObjectPattern)) { const openBraceIndex = callBody.indexOf("{", match.index); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(callBody, openBraceIndex); if (closeBraceIndex === -1) continue; const dataObjectBody = callBody.slice(openBraceIndex, closeBraceIndex + 1); // Job 运行态字段必须由 JobExecutionStore 单写;computed key 无法静态证明安全,按违规处理。 if (/\[[^\]]+\]\s*:/.test(dataObjectBody)) return true; } return false; } function hasForbiddenJobWriteCallBody(callBody) { const runtimeFieldPattern = new RegExp(`\\b(?:${jobRuntimeFields.map(escapeRegExp).join("|")})\\b`); return runtimeFieldPattern.test(callBody) || hasVariableizedOrSpreadJobData(callBody) || hasComputedJobDataKey(callBody); } function hasRawJobRuntimeFieldWrite(sqlSource) { const sql = normalizeRawSqlScanSource(sqlSource); const jobTable = sqlTableReferencePattern("Job"); const identifier = sqlBareIdentifierPattern(); const runtimeField = String.raw`(?:${jobRuntimeFields.map(escapeRegExp).join("|")})`; const maybeQualifiedRuntimeField = String.raw`(?:${identifier}\s*\.\s*)?${runtimeField}`; // PostgreSQL UPDATE 允许目标表后带 `*` 继承标记;扫描时必须先吃掉它再匹配 alias/SET。 const updateJobRuntimeFieldPattern = new RegExp( String.raw`\bUPDATE\s+(?:ONLY\s+)?${jobTable}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\s+SET\b[\s\S]*?\b${maybeQualifiedRuntimeField}\s*=`, "i" ); const updateJobTablePattern = new RegExp( String.raw`\bUPDATE\s+(?:ONLY\s+)?${jobTable}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\s+SET\b`, "i" ); const mergeJobTablePattern = new RegExp( String.raw`\bMERGE\s+INTO\s+(?:ONLY\s+)?${jobTable}(?:\s+(?:AS\s+)?${identifier})?\b`, "i" ); return ( updateJobRuntimeFieldPattern.test(sql) || updateJobTablePattern.test(sql) || mergeJobTablePattern.test(sql) || new RegExp(String.raw`\bINSERT\s+INTO\s+${jobTable}\b`, "i").test(sql) || new RegExp(String.raw`\bDELETE\s+FROM\s+(?:ONLY\s+)?${jobTable}\b`, "i").test(sql) || new RegExp(sqlTruncateTableListPattern(jobTable), "i").test(sql) ); } function findStatementEnd(structure, startIndex) { const semicolonIndex = structure.indexOf(";", startIndex); return semicolonIndex === -1 ? structure.length : semicolonIndex; } function findVariableInitializerSources(content, structure, variableName, beforeIndex) { const sources = []; const escapedName = escapeRegExp(variableName); const declarationPattern = new RegExp( String.raw`\b(?:const|let|var)\s+${escapedName}\b(?:\s*:[^=;]+)?\s*=|\b${escapedName}\s*=`, "g" ); for (const match of structure.matchAll(declarationPattern)) { if (typeof match.index !== "number" || match.index >= beforeIndex) continue; const equalsIndex = structure.indexOf("=", match.index); if (equalsIndex === -1 || equalsIndex >= beforeIndex) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const initializerEnd = findStatementEnd(structure, initializerStart); sources.push({ index: initializerStart, end: initializerEnd, source: content.slice(initializerStart, initializerEnd) }); } return sources; } function findLatestVariableInitializerSource(content, structure, variableName, beforeIndex) { const sources = findVariableInitializerSources(content, structure, variableName, beforeIndex); return sources.length === 0 ? null : sources.at(-1); } function isStaticStringLiteralSource(source) { const trimmed = source.trim(); if (trimmed.length < 2) return false; const quote = trimmed[0]; if (quote !== '"' && quote !== "'" && quote !== "`") return false; const literalEnd = findStringLiteralEnd(trimmed, 0); if (literalEnd !== trimmed.length - 1) return false; // 带表达式的 template literal 不是静态 SQL 片段,不能当成确定表名/字段名展开。 return quote !== "`" || !/\$\{/.test(trimmed); } function isStaticPrismaRawExpressionSource(source) { const trimmed = source.trim(); const rawCallPattern = /\bPrisma\s*\.\s*raw\s*\(/; const rawCallMatch = rawCallPattern.exec(trimmed); if (!rawCallMatch || rawCallMatch.index !== 0) return false; const openParenIndex = trimmed.indexOf("(", rawCallMatch.index + rawCallMatch[0].length - 1); if (openParenIndex === -1) return false; const closeParenIndex = findMatchingParen(maskCommentsOutsideStringLiterals(trimmed), openParenIndex); if (closeParenIndex !== trimmed.length - 1) return false; return isStaticStringLiteralSource(trimmed.slice(openParenIndex + 1, closeParenIndex)); } function isStaticSqlFragmentSource(source) { return isStaticStringLiteralSource(source) || isStaticPrismaRawExpressionSource(source); } function applyTextReplacements(source, replacements) { let output = source; for (const replacement of [...replacements].sort((left, right) => right.start - left.start)) { output = `${output.slice(0, replacement.start)}${replacement.value}${output.slice(replacement.end)}`; } return output; } function staticSqlFragmentReplacementSource(content, structure, variableName, beforeIndex, seenVariables, depth) { if (depth >= rawSqlVariableResolutionMaxDepth || seenVariables.has(variableName)) return null; const initializer = findLatestVariableInitializerSource(content, structure, variableName, beforeIndex); if (!initializer) return null; const nestedSeenVariables = new Set(seenVariables); nestedSeenVariables.add(variableName); const expandedSource = expandStaticSqlSource( content, structure, initializer.source, initializer.index, initializer.end, initializer.index, nestedSeenVariables, depth + 1 ); return isStaticSqlFragmentSource(expandedSource) ? expandedSource.trim() : null; } function expandIdentifiersOutsideStringLiterals(content, structure, source, sourceStart, sourceEnd, seenVariables, depth) { const sourceStructure = structure.slice(sourceStart, sourceEnd); const replacements = []; const identifierPattern = /\b[A-Za-z_$][\w$]*\b/g; for (const match of sourceStructure.matchAll(identifierPattern)) { if (typeof match.index !== "number") continue; const variableName = match[0]; const replacement = staticSqlFragmentReplacementSource( content, structure, variableName, sourceStart + match.index, seenVariables, depth ); if (!replacement) continue; replacements.push({ start: match.index, end: match.index + variableName.length, value: replacement }); } return applyTextReplacements(source, replacements); } function expandPrismaRawIdentifierArguments(content, structure, source, sourceStart, seenVariables, depth) { return source.replace(/\bPrisma\s*\.\s*raw\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/g, (match, variableName, offset) => { const variableOffset = offset + match.lastIndexOf(variableName); const replacement = staticSqlFragmentReplacementSource( content, structure, variableName, sourceStart + variableOffset, seenVariables, depth ); return replacement ? match.replace(variableName, replacement) : match; }); } function expandStaticPrismaRawTemplateIdentifiers(content, structure, source, sourceStart, seenVariables, depth) { return source.replace(/\$\{\s*([A-Za-z_$][\w$]*)\s*\}/g, (match, variableName, offset) => { const variableOffset = offset + match.lastIndexOf(variableName); const replacement = staticSqlFragmentReplacementSource( content, structure, variableName, sourceStart + variableOffset, seenVariables, depth ); // 只有已证明是 Prisma.raw(...) 的片段才当作 SQL 文本;普通 template 变量仍是参数。 return replacement && isStaticPrismaRawExpressionSource(replacement) ? `\${${replacement}}` : match; }); } function expandStaticSqlSource(content, structure, source, sourceStart, sourceEnd, beforeIndex, seenVariables = new Set(), depth = 0) { if (depth >= rawSqlVariableResolutionMaxDepth) return source; // 先展开字符串拼接里的静态中间变量,再补齐 Prisma.sql 模板中 Prisma.raw(...) 的静态参数。 const withConcatenatedIdentifiers = expandIdentifiersOutsideStringLiterals( content, structure, source, sourceStart, sourceEnd, seenVariables, depth ); const withRawArguments = expandPrismaRawIdentifierArguments( content, structure, withConcatenatedIdentifiers, sourceStart, seenVariables, depth ); return expandStaticPrismaRawTemplateIdentifiers(content, structure, withRawArguments, sourceStart, seenVariables, depth); } function markDynamicTemplateSqlExpressions(source) { return source.replace(/\$\{[\s\S]*?\}/g, (match) => { const expressionSource = match.slice(2, -1).trim(); // Prisma.raw 静态字面量已可证明为 identifier;其他模板表达式在 SQL 结构位按动态片段处理。 return isStaticPrismaRawExpressionSource(expressionSource) ? match : ` ${dynamicSqlIdentifierMarker} `; }); } function markDynamicCallExpressionsOutsideStringLiterals(source) { const structure = maskStringLiterals(maskCommentsOutsideStringLiterals(source)); const replacements = []; const callPattern = /\b[A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*\s*\(/g; const skippedCalls = new Set(["Prisma.raw", "Prisma.sql"]); for (const match of structure.matchAll(callPattern)) { if (typeof match.index !== "number") continue; const openParenIndex = structure.indexOf("(", match.index + match[0].length - 1); if (openParenIndex === -1) continue; const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; const callName = match[0] .slice(0, match[0].lastIndexOf("(")) .replace(/\s+/g, ""); if (skippedCalls.has(callName)) continue; // 写 SQL 结构位的函数调用无法静态证明目标表/字段,统一当动态 identifier。 replacements.push({ start: match.index, end: closeParenIndex + 1, value: dynamicSqlIdentifierMarker }); } return applyTextReplacements(source, replacements); } function markDynamicIdentifiersOutsideStringLiterals(source) { const structure = maskStringLiterals(maskCommentsOutsideStringLiterals(source)); const replacements = []; const skippedIdentifiers = new Set(["Prisma", "sql", "raw", "true", "false", "null", "undefined"]); const identifierPattern = /\b[A-Za-z_$][\w$]*\b/g; for (const match of structure.matchAll(identifierPattern)) { if (typeof match.index !== "number" || skippedIdentifiers.has(match[0])) continue; replacements.push({ start: match.index, end: match.index + match[0].length, value: dynamicSqlIdentifierMarker }); } return applyTextReplacements(source, replacements); } function markDynamicSqlIdentifierFragments(source) { return markDynamicIdentifiersOutsideStringLiterals(markDynamicCallExpressionsOutsideStringLiterals(markDynamicTemplateSqlExpressions(source))); } function rawSqlScanCandidateSources(content, structure, source, sourceStart, sourceEnd, beforeIndex) { const expandedSource = expandStaticSqlSource(content, structure, source, sourceStart, sourceEnd, beforeIndex); const dynamicMarkedSource = markDynamicSqlIdentifierFragments(expandedSource); return [...new Set([source, expandedSource, dynamicMarkedSource])]; } function firstCallArgumentSource(content, structure, openParenIndex, closeParenIndex) { const argumentStart = skipWhitespaceForward(structure, openParenIndex + 1); if (argumentStart >= closeParenIndex) return null; let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; for (let cursor = argumentStart; cursor < closeParenIndex; cursor += 1) { const char = structure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (char === "," && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) { return { index: argumentStart, end: cursor, source: content.slice(argumentStart, cursor) }; } } return { index: argumentStart, end: closeParenIndex, source: content.slice(argumentStart, closeParenIndex) }; } function hasDynamicRawSqlWrite(sqlSource) { const sql = normalizeRawSqlScanSource(sqlSource); const dynamicIdentifier = escapeRegExp(dynamicSqlIdentifierMarker); const identifier = sqlBareIdentifierPattern(); const triggerIdentifier = sqlTriggerIdentifierPattern(); const dynamicTableReference = String.raw`(?:${dynamicIdentifier}|${identifier}\s*\.\s*${dynamicIdentifier}|${dynamicIdentifier}\s*\.\s*${identifier})`; const dynamicFieldReference = String.raw`(?:${dynamicIdentifier}|${identifier}\s*\.\s*${dynamicIdentifier}|${dynamicIdentifier}\s*\.\s*${identifier})`; const dynamicFunctionReference = String.raw`(?:${dynamicIdentifier}|${identifier}\s*\.\s*${dynamicIdentifier}|${dynamicIdentifier}\s*\.\s*${identifier})(?:\s*\([^)]*\))?`; const dynamicFunctionList = sqlFunctionListContainingPattern(dynamicFunctionReference); const anyTableReference = sqlAnyTableReferencePattern(); // raw 写 SQL 里的动态表/字段/trigger identifier 无法静态证明边界,必须 fail-closed。 return ( new RegExp(String.raw`\bUPDATE\s+(?:ONLY\s+)?${dynamicTableReference}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\s+SET\b`, "i").test( sql ) || new RegExp(String.raw`\bUPDATE\b[\s\S]*?\bSET\b[\s\S]*?\b${dynamicFieldReference}\s*=`, "i").test(sql) || new RegExp(String.raw`\bINSERT\s+INTO\s+${dynamicTableReference}\b`, "i").test(sql) || new RegExp(String.raw`\bINSERT\s+INTO\b[\s\S]*?\([^)]*\b${dynamicFieldReference}\b[^)]*\)\s+VALUES\b`, "i").test(sql) || new RegExp(String.raw`\bMERGE\s+INTO\s+(?:ONLY\s+)?${dynamicTableReference}(?:\s+(?:AS\s+)?${identifier})?\b`, "i").test( sql ) || new RegExp(String.raw`\bDELETE\s+FROM\s+(?:ONLY\s+)?${dynamicTableReference}\b`, "i").test(sql) || new RegExp(sqlTruncateTableListPattern(dynamicTableReference), "i").test(sql) || new RegExp(sqlDropTableListPattern(dynamicTableReference), "i").test(sql) || new RegExp(String.raw`\bDROP\s+TRIGGER\s+(?:IF\s+EXISTS\s+)?${dynamicFieldReference}\s+ON\s+${anyTableReference}\b`, "i").test( sql ) || new RegExp( String.raw`\bDROP\s+TRIGGER\s+(?:IF\s+EXISTS\s+)?${triggerIdentifier}\s+ON\s+(?:ONLY\s+)?${dynamicTableReference}\b`, "i" ).test(sql) || new RegExp(String.raw`\bALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:ONLY\s+)?${dynamicTableReference}\b`, "i").test(sql) || new RegExp(String.raw`\bALTER\s+TABLE\b[\s\S]*?\bDISABLE\s+TRIGGER\s+${dynamicFieldReference}\b`, "i").test(sql) || // 函数 DDL 可以替换/移除 AuditLog append-only guard,动态函数名必须 fail-closed。 new RegExp(String.raw`\bDROP\s+(?:FUNCTION|ROUTINE)\s+(?:IF\s+EXISTS\s+)?${dynamicFunctionList}(?=\s|;|$)`, "i").test(sql) || new RegExp(String.raw`\bALTER\s+(?:FUNCTION|ROUTINE)\s+(?:IF\s+EXISTS\s+)?${dynamicFunctionReference}(?=\s|;|$)`, "i").test(sql) || new RegExp(String.raw`\bCREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+${dynamicFunctionReference}(?=\s|,|;|$)`, "i").test(sql) ); } function hasDynamicRawSqlWriteCandidate(content, structure, source, sourceStart, sourceEnd, beforeIndex) { return rawSqlScanCandidateSources(content, structure, source, sourceStart, sourceEnd, beforeIndex).some((candidateSource) => hasDynamicRawSqlWrite(candidateSource) ); } function firstCallIdentifierArgument(structure, openParenIndex, closeParenIndex) { const argumentStart = skipWhitespaceForward(structure, openParenIndex + 1); if (argumentStart >= closeParenIndex) return null; const match = /^[A-Za-z_$][\w$]*/.exec(structure.slice(argumentStart, closeParenIndex)); if (!match) return null; const identifierEnd = argumentStart + match[0].length; const nextIndex = skipWhitespaceForward(structure, identifierEnd); // 只跟踪 `raw(query)` 或 `raw(query, ...)` 这种简单变量,避免误读属性访问或函数调用。 if (structure[nextIndex] !== "," && nextIndex !== closeParenIndex) return null; return match[0]; } function nthCallArgumentSource(content, structure, openParenIndex, closeParenIndex, argumentIndex) { const argumentStart = skipWhitespaceForward(structure, openParenIndex + 1); if (argumentStart >= closeParenIndex) return null; let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; let currentArgumentIndex = 0; let currentArgumentStart = argumentStart; for (let cursor = argumentStart; cursor < closeParenIndex; cursor += 1) { const char = structure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (char === "," && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) { if (currentArgumentIndex === argumentIndex) { const end = skipWhitespaceBackward(structure, cursor - 1) + 1; return { index: currentArgumentStart, end, source: content.slice(currentArgumentStart, end) }; } currentArgumentIndex += 1; currentArgumentStart = skipWhitespaceForward(structure, cursor + 1); } } if (currentArgumentIndex !== argumentIndex || currentArgumentStart >= closeParenIndex) return null; const end = skipWhitespaceBackward(structure, closeParenIndex - 1) + 1; return { index: currentArgumentStart, end, source: content.slice(currentArgumentStart, end) }; } const rawSqlExecutorNames = ["$executeRawUnsafe", "$executeRaw", "$queryRawUnsafe", "$queryRaw"]; const rawSqlExecutorPatternSource = String.raw`${staticPrismaPropertyAccessPattern(rawSqlExecutorNames)}\s*(?:<[^>()` + "`" + String.raw`]*>)?\s*`; function rawSqlExecutorPattern() { return new RegExp(rawSqlExecutorPatternSource, "g"); } function rawSqlStructure(content) { // raw executor 需要保留 db["$executeRawUnsafe"] 这类静态 bracket key;SQL 参数本体仍等长遮蔽。 return stripStringLiteralsExceptPropertyKeys(maskCommentsOutsideStringLiterals(content)); } function findOpeningParen(content, closeParenIndex) { let depth = 0; for (let index = closeParenIndex; index >= 0; index -= 1) { const char = content[index]; if (char === ")") depth += 1; if (char === "(") { depth -= 1; if (depth === 0) return index; } } return -1; } function isParenthesizedExpressionOpen(structure, openParenIndex) { const previousIndex = skipWhitespaceBackward(structure, openParenIndex - 1); if (previousIndex < 0) return true; const previous = structure[previousIndex]; if ("([{=,:;!?&|+-*%^~<>".includes(previous)) return true; if (!/[\w$]/.test(previous)) return false; const previousWord = /[A-Za-z_$][\w$]*$/.exec(structure.slice(0, previousIndex + 1))?.[0] ?? ""; return /^(?:return|throw|await|yield|case|delete|typeof|void|new|in|of)$/.test(previousWord); } function skipOptionalTypeArguments(structure, index) { let cursor = skipWhitespaceForward(structure, index); if (structure.startsWith("?.", cursor)) { const optionalCursor = skipWhitespaceForward(structure, cursor + 2); if (structure[optionalCursor] !== "<") return cursor; const optionalTypeEnd = staticTypeArgumentsEnd(structure, optionalCursor); if (optionalTypeEnd === -1) return cursor; const afterOptionalType = skipWhitespaceForward(structure, optionalTypeEnd); return structure[afterOptionalType] === "(" ? afterOptionalType : cursor; } if (structure[cursor] !== "<") return cursor; const typeEnd = staticTypeArgumentsEnd(structure, cursor); if (typeEnd === -1) return cursor; const afterType = skipWhitespaceForward(structure, typeEnd); return structure[afterType] === "(" ? afterType : cursor; } function staticTypeArgumentsEnd(structure, openAngleIndex) { if (structure[openAngleIndex] !== "<") return -1; let depth = 1; let sawTypeToken = false; for (let cursor = openAngleIndex + 1; cursor < structure.length; cursor += 1) { const char = structure[cursor]; if (char === '"' || char === "'" || char === "`") { const literalEnd = findStringLiteralEnd(structure, cursor); if (literalEnd === -1) return -1; cursor = literalEnd; sawTypeToken = true; continue; } if (char === "<") { depth += 1; sawTypeToken = true; continue; } if (char === ">") { depth -= 1; if (depth === 0) return sawTypeToken ? cursor + 1 : -1; continue; } if (char === "(" || char === ")" || char === "{" || char === "}" || char === ";" || char === "\n" || char === "\r") { return -1; } if (!/\s/.test(char)) sawTypeToken = true; } return -1; } function parenthesizedRawSqlOperationStart(structure, rawAccessEnd, closeParenIndex) { let cursor = closeParenIndex; while (structure[cursor] === ")") { const openParenIndex = findOpeningParen(structure, cursor); if (openParenIndex === -1 || !isParenthesizedExpressionOpen(structure, openParenIndex)) return -1; if (rawAccessEnd > cursor) return -1; cursor = skipWhitespaceForward(structure, cursor + 1); } return cursor; } function rawSqlMemberOperation(content, structure, callStart) { const operationStart = skipWhitespaceForward(structure, callStart); const genericCallStart = skipOptionalTypeArguments(structure, operationStart); if (genericCallStart !== operationStart && structure[genericCallStart] === "(") { // raw executor 的 TS 泛型调用 `raw(...)` / `raw?.(...)` 与普通调用等价,SQL 参数仍从调用括号读取。 return { kind: "direct", callStart: genericCallStart }; } if (structure[operationStart] === "(") { return { kind: "direct", callStart: operationStart }; } if (structure[operationStart] === "`") { return { kind: "template", templateStart: operationStart }; } if (structure[operationStart] === ")") { // raw executor 被括号包成 callee 时,真实调用操作在 closing paren 之后。 const parenthesizedOperationStart = parenthesizedRawSqlOperationStart(structure, callStart, operationStart); if (parenthesizedOperationStart !== -1) { return rawSqlMemberOperation(content, structure, parenthesizedOperationStart); } } if (structure.startsWith("?.", operationStart)) { const optionalTargetStart = skipWhitespaceForward(structure, operationStart + 2); if (structure[optionalTargetStart] === "(") { return { kind: "direct", callStart: optionalTargetStart }; } const optionalOperation = staticCallableMemberOperationMatchAt(content, structure, operationStart, ["call", "bind", "apply"]); if (optionalOperation) return optionalOperation; } return staticCallableMemberOperationMatchAt(content, structure, operationStart, ["call", "bind", "apply"]); } function rawSqlBindCloseParenIndex(content, structure, rawMatchEnd, statementEnd = structure.length) { const operation = rawSqlMemberOperation(content, structure, rawMatchEnd); if (operation?.kind !== "bind") return -1; const methodOpenParenIndex = operation.openParenIndex ?? structure.indexOf("(", operation.methodNameEnd); if (methodOpenParenIndex === -1 || methodOpenParenIndex >= statementEnd) return -1; const methodCloseParenIndex = operation.closeParenIndex ?? findMatchingParen(structure, methodOpenParenIndex); return methodCloseParenIndex !== -1 && methodCloseParenIndex <= statementEnd ? methodCloseParenIndex : -1; } function hasLineTerminator(source) { return /[\n\r\u2028\u2029]/.test(source); } function isRawSqlBoundAliasExpressionContinuation(structure, index) { if (index >= structure.length) return false; if (structure.startsWith("?.", index)) return true; // 换行后这些 token 仍可能继续前一条表达式;此时不能把 `.bind(...)` 当作 ASI 结束。 return "([.`+-/*%,".includes(structure[index]); } function rawSqlBoundAliasInitializerEnd(structure, bindCloseParenIndex, statementEnd) { const nextTokenIndex = skipWhitespaceForward(structure, bindCloseParenIndex + 1); if (nextTokenIndex === statementEnd) return statementEnd; const gapAfterBind = structure.slice(bindCloseParenIndex + 1, nextTokenIndex); if (!hasLineTerminator(gapAfterBind)) return -1; if (isRawSqlBoundAliasExpressionContinuation(structure, nextTokenIndex)) return -1; // 无分号合法代码依靠 ASI 在换行处结束 initializer;alias.end 只覆盖 `.bind(...)` 表达式本身。 return bindCloseParenIndex + 1; } function isSimpleRawSqlBoundAliasInitializerPrefix(structure, initializerStart, rawMatchStart) { const prefix = structure.slice(initializerStart, rawMatchStart).trim(); if (prefix.length === 0) return false; // 防止无分号场景把后续语句里的 raw executor 误归到当前声明 initializer。 return !/[;\n\r\u2028\u2029]/.test(prefix) && !/\b(?:const|let|var|await|return|throw|if|for|while|switch|class|function|import|export)\b/.test(prefix); } function isSimpleRawSqlExecutorAliasInitializerPrefix(structure, initializerStart, rawMatchStart) { const prefix = structure.slice(initializerStart, rawMatchStart).trim(); if (prefix.length === 0) return false; // 普通 raw executor alias 只接受静态 member chain,避免把条件表达式或函数返回值误判成 Prisma raw executor。 const staticMemberChain = String.raw`(?:[A-Za-z_$][\w$]*|this)(?:\s*(?:(?:\.|\?\.)\s*[A-Za-z_$][\w$]*|(?:\?\.\s*)?\[\s*(?:"[^"]*"|'[^']*'|` + "`[^`]*`" + String.raw`)\s*\]))*`; return new RegExp(String.raw`^${staticMemberChain}$`).test(prefix); } function rawSqlPlainAliasInitializerEnd(structure, rawMatchEnd, statementEnd) { const nextTokenIndex = skipWhitespaceForward(structure, rawMatchEnd); if (nextTokenIndex === statementEnd) return statementEnd; const gapAfterAccess = structure.slice(rawMatchEnd, nextTokenIndex); if (!hasLineTerminator(gapAfterAccess)) return -1; if (isRawSqlBoundAliasExpressionContinuation(structure, nextTokenIndex)) return -1; // 无分号合法代码依靠 ASI 在换行处结束 initializer;alias.end 只覆盖 raw executor access 本身。 return rawMatchEnd; } function simpleRawSqlAliasInitializerEnd(structure, expressionEnd, statementEnd) { const nextTokenIndex = skipWhitespaceForward(structure, expressionEnd); if (nextTokenIndex === statementEnd) return statementEnd; const gapAfterExpression = structure.slice(expressionEnd, nextTokenIndex); if (!hasLineTerminator(gapAfterExpression)) return -1; if (isRawSqlBoundAliasExpressionContinuation(structure, nextTokenIndex)) return -1; return expressionEnd; } function simpleStaticMemberChainMatchAt(content, structure, index) { const receiverMatch = /^(?:[A-Za-z_$][\w$]*|this)\b/.exec(structure.slice(index)); if (!receiverMatch) return null; let cursor = index + receiverMatch[0].length; while (true) { const nextCursor = skipWhitespaceForward(structure, cursor); if (structure[nextCursor] !== "." && structure[nextCursor] !== "[" && !structure.startsWith("?.", nextCursor)) break; const propertyAccess = staticStaticMemberPropertyAccessMatchAt(content, structure, nextCursor); if (!propertyAccess) break; cursor = propertyAccess.end; } return [structure.slice(index, cursor)]; } function staticStaticMemberPropertyAccessMatchAt(content, structure, index) { let propertyStart = index; if (structure.startsWith("?.", index)) { propertyStart = skipWhitespaceForward(structure, index + 2); } else if (structure[index] === ".") { propertyStart = skipWhitespaceForward(structure, index + 1); } if (structure[propertyStart] === "[") { const closeBracketIndex = findMatchingBracket(structure, propertyStart); if (closeBracketIndex === -1) return null; const expressionStart = propertyStart + 1; const expressionEnd = closeBracketIndex; const values = staticJavaScriptExpressionValues( content, structure, content.slice(expressionStart, expressionEnd), expressionStart, index, new Set(), 0 ); return values.length === 1 ? { index, end: closeBracketIndex + 1, value: values[0] } : null; } if (propertyStart === index && structure[index] !== "." && !structure.startsWith("?.", index)) return null; const nameMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(propertyStart)); return nameMatch ? { index, end: propertyStart + nameMatch[0].length, value: nameMatch[0] } : null; } function rawSqlExecutorAccessMatches(content, structure, startIndex = 0, endIndex = structure.length) { const matches = []; for (let cursor = startIndex; cursor < endIndex; cursor += 1) { if (structure[cursor] !== "." && structure[cursor] !== "[" && !structure.startsWith("?.", cursor)) continue; const match = staticPrismaPropertyAccessMatchAt(content, structure, cursor, rawSqlExecutorNames, cursor); if (!match || match.end > endIndex) continue; matches.push(match); cursor = match.end - 1; } return matches; } function completeRawSqlBoundExecutorAliasExpressionMatch(content, structure, sourceStart, sourceEnd) { const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd); const expressionStart = skipWhitespaceForward(structure, unwrapped.start); const expressionEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (expressionStart >= expressionEnd) return null; for (const rawMatch of rawSqlExecutorAccessMatches(content, structure, expressionStart, expressionEnd)) { if (!isSimpleRawSqlBoundAliasInitializerPrefix(structure, expressionStart, rawMatch.index)) continue; const bindCloseParenIndex = rawSqlBindCloseParenIndex(content, structure, rawMatch.end, expressionEnd); if (bindCloseParenIndex === -1) continue; if (!isAtStaticWrapperExpressionEnd(structure, bindCloseParenIndex + 1, expressionEnd)) continue; return { kind: "rawExecutor", index: rawMatch.index, end: expressionEnd }; } return null; } function completeRawSqlPlainExecutorAliasExpressionMatch(content, structure, sourceStart, sourceEnd) { const unwrapped = unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd); const expressionStart = skipWhitespaceForward(structure, unwrapped.start); const expressionEnd = skipWhitespaceBackward(structure, unwrapped.end - 1) + 1; if (expressionStart >= expressionEnd) return null; for (const rawMatch of rawSqlExecutorAccessMatches(content, structure, expressionStart, expressionEnd)) { if (!isSimpleRawSqlExecutorAliasInitializerPrefix(structure, expressionStart, rawMatch.index)) continue; if (!isAtStaticWrapperExpressionEnd(structure, rawMatch.end, expressionEnd)) continue; return { kind: "rawExecutor", index: rawMatch.index, end: expressionEnd }; } return null; } function hasRawSqlExecutorAccess(content, structure) { return rawSqlExecutorAccessMatches(content, structure).length > 0; } function hasRawSqlExecutorAccessInRange(content, structure, startIndex, endIndex) { const scanStructure = stripParenthesesAroundStaticMemberChains(content, structure); return rawSqlExecutorAccessMatches(content, scanStructure, startIndex, endIndex).length > 0; } function findRawSqlBoundExecutorAliases(content, structure) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const branchRawMatch = staticInitializerBranchMatch( content, structure, initializerStart, statementEnd, completeRawSqlBoundExecutorAliasExpressionMatch ); if (branchRawMatch?.branch) { // mixed ternary/logical 中任一分支返回 raw executor bind,都把 alias 视为 raw executor,防止 safe 分支掩盖危险分支。 aliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: statementEnd }); continue; } for (const rawMatch of rawSqlExecutorAccessMatches(content, structure, initializerStart, statementEnd)) { const rawMatchStart = rawMatch.index; if (!isSimpleRawSqlBoundAliasInitializerPrefix(structure, initializerStart, rawMatchStart)) continue; const rawMatchEnd = rawMatch.end; const bindCloseParenIndex = rawSqlBindCloseParenIndex(content, structure, rawMatchEnd, statementEnd); if (bindCloseParenIndex === -1) continue; const initializerEnd = rawSqlBoundAliasInitializerEnd(structure, bindCloseParenIndex, statementEnd); if (initializerEnd === -1) continue; // 只跟踪同文件的简单 bound executor alias;复杂重赋值/跨函数数据流继续交给后续 review 扩展。 aliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: initializerEnd }); break; } } return aliases; } function findRawSqlAssignedBoundExecutorAliases(content, structure) { const aliases = []; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; if (isMemberIdentifierReference(structure, assignmentMatch.index)) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const branchRawMatch = staticInitializerBranchMatch( content, structure, initializerStart, statementEnd, completeRawSqlBoundExecutorAliasExpressionMatch ); if (branchRawMatch?.branch) { // 后续赋值也按同一规则 fail-closed,避免 `alias = ok ? raw.bind(db) : safe` 绕过。 aliases.push({ name: assignmentMatch[1], index: assignmentMatch.index, end: statementEnd }); continue; } for (const rawMatch of rawSqlExecutorAccessMatches(content, structure, initializerStart, statementEnd)) { const rawMatchStart = rawMatch.index; if (!isSimpleRawSqlBoundAliasInitializerPrefix(structure, initializerStart, rawMatchStart)) continue; const rawMatchEnd = rawMatch.end; const bindCloseParenIndex = rawSqlBindCloseParenIndex(content, structure, rawMatchEnd, statementEnd); if (bindCloseParenIndex === -1) continue; const initializerEnd = rawSqlBoundAliasInitializerEnd(structure, bindCloseParenIndex, statementEnd); if (initializerEnd === -1) continue; // 赋值 alias 覆盖 `let execRaw; execRaw = db.$executeRawUnsafe.bind(db)` 这类后续绑定。 aliases.push({ name: assignmentMatch[1], index: assignmentMatch.index, end: initializerEnd }); break; } } return aliases; } function findRawSqlPlainExecutorAliases(content, structure) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const equalsIndex = structure.indexOf("=", declarationMatch.index); if (equalsIndex === -1) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const branchRawMatch = staticInitializerBranchMatch( content, structure, initializerStart, statementEnd, completeRawSqlPlainExecutorAliasExpressionMatch ); if (branchRawMatch?.branch) { // 普通 raw executor alias 同样不能被 `safe || db.$executeRawUnsafe` 这类逻辑分支隐藏。 aliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: statementEnd }); continue; } for (const rawMatch of rawSqlExecutorAccessMatches(content, structure, initializerStart, statementEnd)) { const rawMatchStart = rawMatch.index; if (!isSimpleRawSqlExecutorAliasInitializerPrefix(structure, initializerStart, rawMatchStart)) continue; const rawMatchEnd = rawMatch.end; const initializerEnd = rawSqlPlainAliasInitializerEnd(structure, rawMatchEnd, statementEnd); if (initializerEnd === -1) continue; // 只跟踪同文件的简单 raw executor alias;复杂重赋值/跨函数数据流继续交给后续 review 扩展。 aliases.push({ name: declarationMatch[1], index: declarationMatch.index, end: initializerEnd }); break; } } return aliases; } function findRawSqlAssignedPlainExecutorAliases(content, structure) { const aliases = []; const assignmentPattern = /\b([A-Za-z_$][\w$]*)\b\s*=/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; if (isMemberIdentifierReference(structure, assignmentMatch.index)) continue; const equalsIndex = structure.indexOf("=", assignmentMatch.index + assignmentMatch[0].lastIndexOf("=")); if (equalsIndex === -1) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const statementEnd = findStatementEnd(structure, initializerStart); const branchRawMatch = staticInitializerBranchMatch( content, structure, initializerStart, statementEnd, completeRawSqlPlainExecutorAliasExpressionMatch ); if (branchRawMatch?.branch) { // 赋值式 plain raw alias 覆盖 `let runRaw; runRaw = maybe || db.$executeRawUnsafe`。 aliases.push({ name: assignmentMatch[1], index: assignmentMatch.index, end: statementEnd }); continue; } for (const rawMatch of rawSqlExecutorAccessMatches(content, structure, initializerStart, statementEnd)) { const rawMatchStart = rawMatch.index; if (!isSimpleRawSqlExecutorAliasInitializerPrefix(structure, initializerStart, rawMatchStart)) continue; const rawMatchEnd = rawMatch.end; const initializerEnd = rawSqlPlainAliasInitializerEnd(structure, rawMatchEnd, statementEnd); if (initializerEnd === -1) continue; // 赋值 alias 覆盖 `let execRaw; execRaw = db.$executeRawUnsafe` 这类后续绑定。 aliases.push({ name: assignmentMatch[1], index: assignmentMatch.index, end: initializerEnd }); break; } } return aliases; } function destructuringParts(structure, startIndex, endIndex) { const parts = []; let partStart = startIndex; let braceDepth = 0; let bracketDepth = 0; let parenDepth = 0; for (let cursor = startIndex; cursor < endIndex; cursor += 1) { const char = structure[cursor]; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char !== "," || braceDepth !== 0 || bracketDepth !== 0 || parenDepth !== 0) continue; parts.push({ start: partStart, end: cursor }); partStart = cursor + 1; } parts.push({ start: partStart, end: endIndex }); return parts; } function destructuredAliasNamesForStaticKeys(content, structure, bindingStart, bindingEnd, keys, beforeIndex) { const names = []; const allowedKeys = new Set(keys); for (const part of destructuringParts(structure, bindingStart, bindingEnd)) { const key = staticDestructuringPropertyKeyValue(content, structure, part.start, part.end, beforeIndex); if (!allowedKeys.has(key)) continue; const name = destructuringAliasName(structure, part.start, part.end); if (name) names.push(name); } return names; } function staticDestructuringPropertyKeyValue(content, structure, partStart, partEnd, beforeIndex) { const keyStart = skipWhitespaceForward(structure, partStart); const keyEnd = destructuringPropertyKeyEnd(structure, keyStart, partEnd); if (keyEnd === -1) return null; const expressionStart = structure[keyStart] === "[" ? keyStart + 1 : keyStart; const expressionEnd = structure[keyStart] === "[" ? keyEnd - 1 : keyEnd; const keySource = content.slice(expressionStart, expressionEnd); if (structure[keyStart] !== "[" && /^[A-Za-z_$][\w$]*$/.test(keySource.trim())) return keySource.trim(); return staticJavaScriptExpressionValues(content, structure, keySource, expressionStart, beforeIndex, new Set(), 0)[0] ?? null; } function destructuringPropertyKeyEnd(structure, keyStart, partEnd) { if (keyStart >= partEnd) return -1; if (structure[keyStart] === "[") { const closeBracketIndex = findMatchingBracket(structure, keyStart); if (closeBracketIndex === -1 || closeBracketIndex >= partEnd) return -1; const afterKey = skipWhitespaceForward(structure, closeBracketIndex + 1); return structure[afterKey] === ":" ? closeBracketIndex + 1 : -1; } const literalEnd = findStringLiteralEnd(structure, keyStart); if ((structure[keyStart] === '"' || structure[keyStart] === "'" || structure[keyStart] === "`") && literalEnd !== -1 && literalEnd < partEnd) { const afterKey = skipWhitespaceForward(structure, literalEnd + 1); return structure[afterKey] === ":" ? literalEnd + 1 : -1; } const identifierMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(keyStart, partEnd)); if (!identifierMatch) return -1; const keyEnd = keyStart + identifierMatch[0].length; const afterKey = skipWhitespaceForward(structure, keyEnd); if (structure[afterKey] === ":" || afterKey === partEnd) return keyEnd; return -1; } function destructuringAliasName(structure, partStart, partEnd) { const keyStart = skipWhitespaceForward(structure, partStart); const keyEnd = destructuringPropertyKeyEnd(structure, keyStart, partEnd); if (keyEnd === -1) return null; const afterKey = skipWhitespaceForward(structure, keyEnd); if (structure[afterKey] !== ":") { const shorthandName = structure.slice(keyStart, keyEnd).trim(); return /^[A-Za-z_$][\w$]*$/.test(shorthandName) ? shorthandName : null; } const aliasStart = skipWhitespaceForward(structure, afterKey + 1); const aliasMatch = /^[A-Za-z_$][\w$]*/.exec(structure.slice(aliasStart, partEnd)); if (!aliasMatch) return null; const aliasEnd = aliasStart + aliasMatch[0].length; const afterAlias = skipWhitespaceForward(structure, aliasEnd); return afterAlias === partEnd ? aliasMatch[0] : null; } function findRawSqlDestructuredExecutorAliases(content, structure) { const aliases = []; const declarationPattern = /\b(?:const|let|var)\s*\{/g; for (const declarationMatch of structure.matchAll(declarationPattern)) { if (typeof declarationMatch.index !== "number") continue; const openBraceIndex = structure.indexOf("{", declarationMatch.index); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) continue; const names = destructuredAliasNamesForStaticKeys( content, structure, openBraceIndex + 1, closeBraceIndex, rawSqlExecutorNames, declarationMatch.index ); if (names.length === 0) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] === ":") { const equalsAfterType = structure.indexOf("=", cursor + 1); const semicolonAfterType = structure.indexOf(";", cursor + 1); if (equalsAfterType === -1 || (semicolonAfterType !== -1 && semicolonAfterType < equalsAfterType)) continue; cursor = skipWhitespaceForward(structure, equalsAfterType + 1); } else { if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); } const receiverMatch = simpleStaticMemberChainMatchAt(content, structure, cursor); if (!receiverMatch) continue; const expressionEnd = cursor + receiverMatch[0].length; const statementEnd = findStatementEnd(structure, cursor); const aliasEnd = simpleRawSqlAliasInitializerEnd(structure, expressionEnd, statementEnd); if (aliasEnd === -1) continue; // 解构 alias 只覆盖简单 receiver,避免把复杂运行时对象误判为 Prisma raw executor。 for (const name of names) { aliases.push({ name, index: declarationMatch.index, end: aliasEnd }); } } return aliases; } function findRawSqlAssignedDestructuredExecutorAliases(content, structure) { const aliases = []; const assignmentPattern = /\(\s*\{/g; for (const assignmentMatch of structure.matchAll(assignmentPattern)) { if (typeof assignmentMatch.index !== "number") continue; const openParenIndex = structure.indexOf("(", assignmentMatch.index); if (openParenIndex === -1 || !isParenthesizedExpressionOpen(structure, openParenIndex)) continue; const openBraceIndex = structure.indexOf("{", openParenIndex); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); const closeParenIndex = findMatchingParen(structure, openParenIndex); if (closeBraceIndex === -1 || closeParenIndex === -1 || closeBraceIndex > closeParenIndex) continue; const names = destructuredAliasNamesForStaticKeys( content, structure, openBraceIndex + 1, closeBraceIndex, rawSqlExecutorNames, assignmentMatch.index ); if (names.length === 0) continue; let cursor = skipWhitespaceForward(structure, closeBraceIndex + 1); if (structure[cursor] !== "=") continue; cursor = skipWhitespaceForward(structure, cursor + 1); const receiverMatch = simpleStaticMemberChainMatchAt(content, structure, cursor); if (!receiverMatch) continue; const expressionEnd = cursor + receiverMatch[0].length; if (skipWhitespaceForward(structure, expressionEnd) !== closeParenIndex) continue; const statementEnd = findStatementEnd(structure, openParenIndex); const aliasEnd = simpleRawSqlAliasInitializerEnd(structure, closeParenIndex + 1, statementEnd); if (aliasEnd === -1) continue; // 声明后的解构赋值 alias 覆盖 `let execRaw; ({ $executeRawUnsafe: execRaw } = db)`。 for (const name of names) { aliases.push({ name, index: assignmentMatch.index, end: aliasEnd }); } } return aliases; } function isMemberIdentifierReference(structure, identifierIndex) { const previousIndex = skipWhitespaceBackward(structure, identifierIndex - 1); return previousIndex >= 0 && structure[previousIndex] === "."; } function firstArrayElementSource(content, structure, sourceStart, sourceEnd) { return nthArrayElementSource(content, structure, { index: sourceStart, end: sourceEnd }, 0); } function nthArrayElementSource(content, structure, arrayArgument, elementIndex) { if (!arrayArgument) return null; const sourceStart = arrayArgument.index; const sourceEnd = arrayArgument.end; const arrayStart = skipWhitespaceForward(structure, sourceStart); const arrayEnd = skipWhitespaceBackward(structure, sourceEnd - 1) + 1; if (arrayStart >= arrayEnd || structure[arrayStart] !== "[") return null; const closeBracketIndex = findMatchingBracket(structure, arrayStart); if (closeBracketIndex === -1 || closeBracketIndex !== arrayEnd - 1) return null; return nthCallArgumentSource(content, structure, arrayStart, closeBracketIndex, elementIndex); } function rawSqlInvocationSourcesFromArrayArgument(content, structure, arrayArgument, beforeIndex) { const sqlArgument = firstArrayElementSource(content, structure, arrayArgument.index, arrayArgument.end); return sqlArgument ? [{ ...sqlArgument, beforeIndex }] : []; } function rawSqlExecutorDirectAliases(content, structure) { return findRawSqlBoundExecutorAliases(content, structure) .concat(findRawSqlAssignedBoundExecutorAliases(content, structure)) .concat(findRawSqlPlainExecutorAliases(content, structure)) .concat(findRawSqlAssignedPlainExecutorAliases(content, structure)) .concat(findRawSqlDestructuredExecutorAliases(content, structure)) .concat(findRawSqlAssignedDestructuredExecutorAliases(content, structure)); } function rawSqlExecutorAliases(content, structure) { const directAliases = propagateStaticIdentifierAliases(structure, rawSqlExecutorDirectAliases(content, structure), (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end })); const wrappers = rawSqlExecutorWrapperSources(content, structure, directAliases); const aliases = findRawSqlBoundExecutorAliases(content, structure) .concat(findRawSqlAssignedBoundExecutorAliases(content, structure)) .concat(findRawSqlPlainExecutorAliases(content, structure)) .concat(findRawSqlAssignedPlainExecutorAliases(content, structure)) .concat(findRawSqlDestructuredExecutorAliases(content, structure)) .concat(findRawSqlAssignedDestructuredExecutorAliases(content, structure)) .concat(findStaticWrapperExtractionAliases(content, structure, wrappers)); return propagateStaticIdentifierAliases(structure, aliases, (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end })); } function isRawSqlExecutorAliasTarget(structure, targetArgument, aliases) { const aliasName = simpleIdentifierSourceName(structure, targetArgument.index, targetArgument.end); if (!aliasName) return false; // Reflect.apply 只能使用调用点之前已建立的同文件 raw executor alias。 return Boolean(latestStaticAliasRecordAt(structure, aliases, aliasName, targetArgument.index)); } function isRawSqlExecutorTargetArgument(content, structure, targetArgument, aliases) { return ( targetArgument && (hasRawSqlExecutorAccessInRange(content, structure, targetArgument.index, targetArgument.end) || isRawSqlExecutorAliasTarget(structure, targetArgument, aliases)) ); } function rawSqlInvocationSourcesFromFunctionPrototypeArrayArgument(content, structure, arrayArgument, methodKind, beforeIndex) { if (methodKind === "call") { const sqlArgument = nthArrayElementSource(content, structure, arrayArgument, 1); return sqlArgument ? [{ ...sqlArgument, beforeIndex }] : []; } if (methodKind === "apply") { const nestedArrayArgument = nthArrayElementSource(content, structure, arrayArgument, 1); return nestedArrayArgument ? rawSqlInvocationSourcesFromArrayArgument(content, structure, nestedArrayArgument, beforeIndex) : []; } return []; } function rawSqlFunctionPrototypeInvocationSources(content, structure, builtinInvokerAliases) { const sources = []; const aliases = rawSqlExecutorAliases(content, structure); const boundTargetAliases = findFunctionPrototypeBoundTargetAliases(content, structure); for (const invocation of functionPrototypeCallInvocationMatches(content, structure, builtinInvokerAliases)) { const targetArgument = functionPrototypeEquivalentArgumentSource(content, structure, invocation, 0); if (!isRawSqlExecutorTargetArgument(content, structure, targetArgument, aliases)) continue; if (invocation.methodKind === "call") { // Function.prototype.call.call(raw, thisArg, sql, ...) 的 SQL 是外层调用第三参。 const sqlArgument = functionPrototypeEquivalentArgumentSource(content, structure, invocation, 2); if (sqlArgument) sources.push({ ...sqlArgument, beforeIndex: invocation.index }); continue; } // Function.prototype.apply.call(raw, thisArg, [sql, ...]) 的 SQL 在第三参数数组首项。 const arrayArgument = functionPrototypeEquivalentArgumentSource(content, structure, invocation, 2); if (arrayArgument) sources.push(...rawSqlInvocationSourcesFromArrayArgument(content, structure, arrayArgument, invocation.index)); } for (const invocation of functionPrototypeBoundTargetInvocationMatches(content, structure, boundTargetAliases)) { if (!isRawSqlExecutorTargetArgument(content, structure, invocation.targetArgument, aliases)) continue; if (invocation.methodKind === "call") { // Function.prototype.call.bind(raw)(thisArg, sql, ...) 的 SQL 是绑定后调用的第二参。 const sqlArgument = nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, 1); if (sqlArgument) sources.push({ ...sqlArgument, beforeIndex: invocation.index }); continue; } // Function.prototype.apply.bind(raw)(thisArg, [sql, ...]) 的 SQL 在绑定后调用第二参数数组首项。 const arrayArgument = nthCallArgumentSource(content, structure, invocation.openParenIndex, invocation.closeParenIndex, 1); if (arrayArgument) sources.push(...rawSqlInvocationSourcesFromArrayArgument(content, structure, arrayArgument, invocation.index)); } return sources; } function rawSqlReflectApplyInvocationSources(content, structure, builtinInvokerAliases) { const sources = []; const aliases = rawSqlExecutorAliases(content, structure); for (const invocation of reflectApplyInvocationMatches(content, structure, builtinInvokerAliases)) { const targetArgument = reflectApplyEquivalentArgumentSource(content, structure, invocation, 0); if (!targetArgument) continue; const functionPrototypeMethod = functionPrototypeMethodKindFromArgument(content, structure, targetArgument, builtinInvokerAliases); if (functionPrototypeMethod) { const invokedFunctionArgument = reflectApplyEquivalentArgumentSource(content, structure, invocation, 1); if (!isRawSqlExecutorTargetArgument(content, structure, invokedFunctionArgument, aliases)) continue; const arrayArgument = reflectApplyEquivalentArgumentSource(content, structure, invocation, 2); if (!arrayArgument) continue; sources.push(...rawSqlInvocationSourcesFromFunctionPrototypeArrayArgument(content, structure, arrayArgument, functionPrototypeMethod, invocation.index)); continue; } if (!isRawSqlExecutorTargetArgument(content, structure, targetArgument, aliases)) continue; const arrayArgument = reflectApplyEquivalentArgumentSource(content, structure, invocation, 2); if (!arrayArgument) continue; sources.push(...rawSqlInvocationSourcesFromArrayArgument(content, structure, arrayArgument, invocation.index)); } return sources; } function rawSqlInvocationSourcesFromOperation(content, structure, operation, beforeIndex) { const sources = []; if (!operation) return sources; if (operation.kind === "direct") { const closeParenIndex = findMatchingParen(structure, operation.callStart); if (closeParenIndex === -1) return sources; const sqlArgument = nthCallArgumentSource(content, structure, operation.callStart, closeParenIndex, 0); if (sqlArgument) sources.push({ ...sqlArgument, beforeIndex }); return sources; } if (operation.kind === "template") { const templateEnd = findStringLiteralEnd(content, operation.templateStart); if (templateEnd !== -1) { sources.push({ source: content.slice(operation.templateStart, templateEnd + 1), index: operation.templateStart, end: templateEnd + 1, beforeIndex }); } return sources; } if (operation.kind !== "call" && operation.kind !== "bind" && operation.kind !== "apply") return sources; const methodOpenParenIndex = operation.openParenIndex ?? structure.indexOf("(", operation.methodNameEnd); if (methodOpenParenIndex === -1) return sources; const methodCloseParenIndex = operation.closeParenIndex ?? findMatchingParen(structure, methodOpenParenIndex); if (methodCloseParenIndex === -1) return sources; if (operation.kind === "call") { // .call(thisArg, sql, ...) 的 SQL 是第二个参数。 const sqlArgument = nthCallArgumentSource(content, structure, methodOpenParenIndex, methodCloseParenIndex, 1); if (sqlArgument) sources.push({ ...sqlArgument, beforeIndex }); return sources; } if (operation.kind === "apply") { // .apply(thisArg, [sql, ...]) 的 SQL 在参数数组首项,和 Reflect.apply 共享同一提取语义。 const arrayArgument = nthCallArgumentSource(content, structure, methodOpenParenIndex, methodCloseParenIndex, 1); return arrayArgument ? rawSqlInvocationSourcesFromArrayArgument(content, structure, arrayArgument, beforeIndex) : sources; } let boundCallStart = skipWhitespaceForward(structure, methodCloseParenIndex + 1); if (structure.startsWith("?.", boundCallStart)) { boundCallStart = skipWhitespaceForward(structure, boundCallStart + 2); } if (structure[boundCallStart] !== "(") return sources; const boundCloseParenIndex = findMatchingParen(structure, boundCallStart); if (boundCloseParenIndex === -1) return sources; const sqlArgument = nthCallArgumentSource(content, structure, boundCallStart, boundCloseParenIndex, 0); if (sqlArgument) sources.push({ ...sqlArgument, beforeIndex }); return sources; } function rawSqlSourceMatcher(aliases = []) { return (content, structure, sourceStart, sourceEnd) => { const accessMatches = rawSqlExecutorAccessMatches(content, structure, sourceStart, sourceEnd); for (const rawMatch of accessMatches) { if (!isSimpleRawSqlExecutorAliasInitializerPrefix(structure, sourceStart, rawMatch.index)) continue; const bindCloseParenIndex = rawSqlBindCloseParenIndex(content, structure, rawMatch.end, sourceEnd); if (bindCloseParenIndex !== -1 && isAtStaticWrapperExpressionEnd(structure, bindCloseParenIndex + 1, sourceEnd)) { return { kind: "rawExecutor", index: rawMatch.index, end: bindCloseParenIndex + 1 }; } if (isAtStaticWrapperExpressionEnd(structure, rawMatch.end, sourceEnd)) { return { kind: "rawExecutor", index: rawMatch.index, end: rawMatch.end }; } } return staticIdentifierAliasSourceMatchAt(structure, aliases, sourceStart, sourceEnd, sourceStart); }; } function rawSqlExecutorWrapperSources(content, structure, aliases = []) { return staticWrapperSources(content, structure, rawSqlSourceMatcher(aliases)); } function rawSqlWrappedExecutorInvocationSources(content, structure) { const sources = []; const directAliases = propagateStaticIdentifierAliases(structure, rawSqlExecutorDirectAliases(content, structure), (_sourceAlias, initializer) => ({ name: initializer.name, index: initializer.index, end: initializer.end })); for (const usage of staticWrapperSourceUsageMatches(content, structure, rawSqlExecutorWrapperSources(content, structure, directAliases))) { const operation = rawSqlMemberOperation(content, structure, usage.end); sources.push(...rawSqlInvocationSourcesFromOperation(content, structure, operation, usage.index)); } return sources; } function rawSqlAliasInvocationSources(content, structure, aliases) { const sources = []; for (const alias of aliases) { const aliasCallPattern = new RegExp(String.raw`(?:^|[^\w$])(${escapeRegExp(alias.name)})\b\s*(?:<[^>()` + "`" + String.raw`]*>)?\s*`, "g"); for (const aliasMatch of structure.matchAll(aliasCallPattern)) { if (typeof aliasMatch.index !== "number" || aliasMatch.index <= alias.end) continue; const aliasIndex = aliasMatch.index + aliasMatch[0].indexOf(alias.name); if (aliasIndex <= alias.end) continue; if (isMemberIdentifierReference(structure, aliasIndex)) continue; const operationStart = skipWhitespaceForward(structure, aliasIndex + alias.name.length); const operation = rawSqlMemberOperation(content, structure, operationStart); sources.push(...rawSqlInvocationSourcesFromOperation(content, structure, operation, aliasIndex)); } } return sources; } function rawSqlBoundAliasInvocationSources(content, structure) { return rawSqlAliasInvocationSources( content, structure, findRawSqlBoundExecutorAliases(content, structure).concat(findRawSqlAssignedBoundExecutorAliases(content, structure)) ); } function rawSqlPlainAliasInvocationSources(content, structure) { return rawSqlAliasInvocationSources( content, structure, findRawSqlPlainExecutorAliases(content, structure) .concat(findRawSqlAssignedPlainExecutorAliases(content, structure)) .concat(findRawSqlDestructuredExecutorAliases(content, structure)) .concat(findRawSqlAssignedDestructuredExecutorAliases(content, structure)) ); } function rawSqlInvocationSources(content, structure = rawSqlStructure(content)) { const scanStructure = stripParenthesesAroundStaticMemberChains(content, structure); const sources = []; const builtinInvokerAliases = findStaticBuiltinInvokerAliases(content, scanStructure); for (const match of rawSqlExecutorAccessMatches(content, scanStructure)) { let callStart = skipWhitespaceForward(scanStructure, match.end); const operation = rawSqlMemberOperation(content, scanStructure, callStart); sources.push(...rawSqlInvocationSourcesFromOperation(content, scanStructure, operation, match.index)); } return sources.concat( rawSqlReflectApplyInvocationSources(content, scanStructure, builtinInvokerAliases), rawSqlFunctionPrototypeInvocationSources(content, scanStructure, builtinInvokerAliases), rawSqlWrappedExecutorInvocationSources(content, scanStructure), rawSqlAliasInvocationSources(content, scanStructure, rawSqlExecutorAliases(content, scanStructure)) ); } function simpleIdentifierSourceName(structure, sourceStart, sourceEnd) { const identifierStart = skipWhitespaceForward(structure, sourceStart); const identifierEnd = skipWhitespaceBackward(structure, sourceEnd - 1) + 1; if (identifierStart >= identifierEnd) return null; const source = structure.slice(identifierStart, identifierEnd); return /^[A-Za-z_$][\w$]*$/.test(source) ? source : null; } function hasRawSqlInvocationMatching(content, predicate, structure = rawSqlStructure(content)) { for (const invocation of rawSqlInvocationSources(content, structure)) { const { source, index, end, beforeIndex } = invocation; if (rawSqlScanCandidateSources(content, structure, source, index, end, beforeIndex).some((candidateSource) => predicate(candidateSource))) { return true; } const variableName = simpleIdentifierSourceName(structure, index, end); if ( variableName && findVariableInitializerSources(content, structure, variableName, beforeIndex).some(({ source: variableSource, index: variableIndex, end: variableEnd }) => rawSqlScanCandidateSources(content, structure, variableSource, variableIndex, variableEnd, beforeIndex).some((candidateSource) => predicate(candidateSource) ) ) ) { return true; } } return false; } function hasDynamicIdentifierRawSqlWrite(content) { return hasRawSqlInvocationMatching(content, hasDynamicRawSqlWrite); } function hasRawJobStatusWrite(content) { // raw SQL 检测仍从原始 content 切片读取 SQL 字符串;structure 只负责可靠找到调用边界。 return hasRawSqlInvocationMatching(content, hasRawJobRuntimeFieldWrite); } function hasForbiddenRawJobStatusWrite(filePath, content) { // migration.sql 没有 Prisma raw 调用包装,必须直接扫描 SQL 文件本体。 if (filePath.endsWith(".sql")) return hasRawJobRuntimeFieldWrite(content); return hasRawJobStatusWrite(content); } function hasRawAuditLogMutation(sqlSource, options = {}) { const sql = normalizeRawSqlScanSource(sqlSource); const auditLogTable = sqlTableReferencePattern("AuditLog"); const auditLogAppendOnlyFunction = sqlSpecificFunctionReferencePattern(auditLogAppendOnlyFunctionName); const auditLogAppendOnlyFunctionList = sqlFunctionListContainingPattern(auditLogAppendOnlyFunction); const identifier = sqlBareIdentifierPattern(); const triggerIdentifier = sqlTriggerIdentifierPattern(); // PostgreSQL UPDATE 允许目标表后带 `*` 继承标记;AuditLog append-only 扫描必须覆盖该合法语法。 const createsPlainAuditLogAppendOnlyFunction = new RegExp( String.raw`\bCREATE\s+FUNCTION\s+${auditLogAppendOnlyFunction}\b`, "i" ).test(sql); const createsOrReplacesAuditLogAppendOnlyFunction = new RegExp( String.raw`\bCREATE\s+OR\s+REPLACE\s+FUNCTION\s+${auditLogAppendOnlyFunction}\b`, "i" ).test(sql); return ( new RegExp(String.raw`\bUPDATE\s+(?:ONLY\s+)?${auditLogTable}(?:\s*\*)?(?:\s+(?:AS\s+)?${identifier})?\s+SET\b`, "i").test(sql) || new RegExp(String.raw`\bDELETE\s+FROM\s+(?:ONLY\s+)?${auditLogTable}\b`, "i").test(sql) || new RegExp( String.raw`\bINSERT\s+INTO\s+(?:ONLY\s+)?${auditLogTable}\b[\s\S]*?\bON\s+CONFLICT\b[\s\S]*?\bDO\s+UPDATE\b`, "i" ).test(sql) || new RegExp(String.raw`\bMERGE\s+INTO\s+(?:ONLY\s+)?${auditLogTable}(?:\s+(?:AS\s+)?${identifier})?\b`, "i").test(sql) || new RegExp(sqlTruncateTableListPattern(auditLogTable), "i").test(sql) || new RegExp(sqlDropTableListPattern(auditLogTable), "i").test(sql) || new RegExp(String.raw`\bDROP\s+TRIGGER\s+(?:IF\s+EXISTS\s+)?${triggerIdentifier}\s+ON\s+(?:ONLY\s+)?${auditLogTable}\b`, "i").test(sql) || new RegExp( String.raw`\bALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:ONLY\s+)?${auditLogTable}\s+(?:DISABLE\s+(?:TRIGGER|RULE)\b|DROP\b|RENAME\b)`, "i" ).test(sql) || new RegExp(String.raw`\bDROP\s+(?:FUNCTION|ROUTINE)\s+(?:IF\s+EXISTS\s+)?${auditLogAppendOnlyFunctionList}(?=\s|;|$)`, "i").test( sql ) || (!options.allowAuditLogAppendOnlyFunctionPlainCreate && createsPlainAuditLogAppendOnlyFunction) || (!options.allowAuditLogAppendOnlyFunctionCreateOrReplace && createsOrReplacesAuditLogAppendOnlyFunction) || new RegExp(String.raw`\bALTER\s+(?:FUNCTION|ROUTINE)\s+(?:IF\s+EXISTS\s+)?${auditLogAppendOnlyFunction}(?=\s|;|$)`, "i").test(sql) ); } function hasForbiddenRawAuditLogMutation(filePath, content, structure) { const auditSqlOptions = auditLogAppendOnlyFunctionCreateOptions(filePath, content); if (filePath.endsWith(".sql")) return hasRawAuditLogMutation(content, auditSqlOptions); for (const invocation of rawSqlInvocationSources(content, structure)) { const { source, index, end, beforeIndex } = invocation; const hasForbiddenSql = rawSqlScanCandidateSources(content, structure, source, index, end, beforeIndex).some((candidateSource) => hasRawAuditLogMutation(candidateSource, auditSqlOptions) ) || (() => { const variableName = simpleIdentifierSourceName(structure, index, end); return ( variableName !== null && findVariableInitializerSources(content, structure, variableName, beforeIndex).some( ({ source: variableSource, index: variableIndex, end: variableEnd }) => rawSqlScanCandidateSources(content, structure, variableSource, variableIndex, variableEnd, beforeIndex).some((candidateSource) => hasRawAuditLogMutation(candidateSource, auditSqlOptions) ) ) ); })(); if (!hasForbiddenSql) continue; if (isAllowedAuditTriggerAssertion(filePath, content, structure, beforeIndex)) continue; return true; } return false; } function jobExecutionStoreClassSpans(content) { const spans = []; const structure = maskStringLiterals(maskCommentsOutsideStringLiterals(content)); const classPattern = /\b(?:export\s+)?class\s+JobExecutionStore\b/g; for (const match of structure.matchAll(classPattern)) { if (typeof match.index !== "number") continue; const openBraceIndex = structure.indexOf("{", match.index + match[0].length); if (openBraceIndex === -1) continue; const closeBraceIndex = findMatchingBrace(structure, openBraceIndex); if (closeBraceIndex === -1) continue; spans.push({ start: match.index, end: closeBraceIndex + 1 }); } return spans; } function maskSpans(content, spans) { if (spans.length === 0) return content; let output = ""; let cursor = 0; for (const span of spans.toSorted((left, right) => left.start - right.start)) { if (span.start < cursor) continue; output += content.slice(cursor, span.start); output += " ".repeat(span.end - span.start); cursor = span.end; } return output + content.slice(cursor); } function maskJobExecutionStoreClassBodies(content) { // 只遮蔽 JobExecutionStore 自身;同文件里的 API service / controller 仍要按普通边界扫描。 return maskSpans(content, jobExecutionStoreClassSpans(content)); } function hasForbiddenMigrationGuardSetter(content) { const sql = stripSqlComments(content); return /set_config\s*\(|SET\s+LOCAL/i.test(sql); } function hasStateTransitionSpecifier(value) { return /state-transition(?:\/index\.js)?/.test(value); } function unwrapCompleteParenthesizedJavaScriptExpression(structure, sourceStart, sourceEnd) { let trimmedStart = skipWhitespaceForward(structure, sourceStart); let trimmedEnd = skipWhitespaceBackward(structure, sourceEnd - 1) + 1; while (trimmedStart < trimmedEnd && structure[trimmedStart] === "(") { const closeParenIndex = findMatchingParen(structure, trimmedStart); if (closeParenIndex !== trimmedEnd - 1) break; const innerStart = skipWhitespaceForward(structure, trimmedStart + 1); const innerEnd = skipWhitespaceBackward(structure, closeParenIndex - 1) + 1; if (innerStart >= innerEnd) break; // 只剥离完整包住表达式的括号,避免改变 ("a") + b 这类拼接分片边界。 trimmedStart = innerStart; trimmedEnd = innerEnd; } return { start: trimmedStart, end: trimmedEnd }; } function decodeStaticTemplateLiteralExpressionValue(content, structure, sourceStart, sourceEnd, beforeIndex, seenVariables, depth) { if (content[sourceStart] !== "`") return null; const templateEnd = findStringLiteralEnd(content, sourceStart); if (templateEnd !== sourceEnd - 1) return null; let output = ""; let cursor = sourceStart + 1; while (cursor < templateEnd) { if (content[cursor] === "\\") { if (cursor + 1 < templateEnd) { output += decodeJavaScriptStringLiteralBody(content.slice(cursor, cursor + 2)); cursor += 2; continue; } return null; } if (content[cursor] === "$" && content[cursor + 1] === "{") { const expressionStart = cursor + 2; const expressionEnd = findMatchingBrace(content, cursor + 1); if (expressionEnd === -1 || expressionEnd > templateEnd) return null; const expressionStructure = structure.slice(0, expressionStart) + content.slice(expressionStart, expressionEnd) + structure.slice(expressionEnd); const values = staticJavaScriptExpressionValues( content, expressionStructure, content.slice(expressionStart, expressionEnd), expressionStart, beforeIndex, seenVariables, depth + 1 ); if (values.length !== 1) return null; output += values[0]; cursor = expressionEnd + 1; continue; } output += content[cursor]; cursor += 1; } return output; } function staticJavaScriptExpressionValues(content, structure, source, sourceStart, beforeIndex, seenVariables, depth) { if (depth >= rawSqlVariableResolutionMaxDepth) return []; const { start: trimmedStart, end: trimmedEnd } = unwrapCompleteParenthesizedJavaScriptExpression( structure, sourceStart, sourceStart + source.length ); if (trimmedStart >= trimmedEnd) return []; const expression = content.slice(trimmedStart, trimmedEnd); const literalValue = decodeStaticJavaScriptStringLiteralSource(expression); if (literalValue !== null) return [literalValue]; const templateValue = decodeStaticTemplateLiteralExpressionValue(content, structure, trimmedStart, trimmedEnd, beforeIndex, seenVariables, depth); if (templateValue !== null) return [templateValue]; const identifierName = simpleIdentifierSourceName(structure, trimmedStart, trimmedEnd); if (identifierName) { return staticStringVariableInitializers(content, structure, identifierName, beforeIndex, seenVariables, depth + 1); } const expressionStructure = structure.slice(trimmedStart, trimmedEnd); let parts = []; let partStart = 0; let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; for (let cursor = 0; cursor < expressionStructure.length; cursor += 1) { const char = expressionStructure[cursor]; if (char === "(") parenDepth += 1; if (char === ")") parenDepth -= 1; if (char === "{") braceDepth += 1; if (char === "}") braceDepth -= 1; if (char === "[") bracketDepth += 1; if (char === "]") bracketDepth -= 1; if (char !== "+" || parenDepth !== 0 || braceDepth !== 0 || bracketDepth !== 0) continue; parts.push({ start: trimmedStart + partStart, end: trimmedStart + cursor }); partStart = cursor + 1; } if (parts.length === 0) return []; parts.push({ start: trimmedStart + partStart, end: trimmedEnd }); const values = []; for (const part of parts) { const partSource = content.slice(part.start, part.end); const partValues = staticJavaScriptExpressionValues(content, structure, partSource, part.start, beforeIndex, seenVariables, depth + 1); if (partValues.length !== 1) return []; values.push(partValues[0]); } // 只折叠全静态字符串拼接;任何动态片段都保持未知,不影响其它动态 import。 return [values.join("")]; } function staticStringVariableInitializers(content, structure, variableName, beforeIndex, seenVariables = new Set(), depth = 0) { if (depth >= rawSqlVariableResolutionMaxDepth || seenVariables.has(variableName)) return []; const values = []; const nestedSeenVariables = new Set(seenVariables); nestedSeenVariables.add(variableName); const initializerPatterns = [ new RegExp(String.raw`\b(?:const|let|var)\s+${escapeRegExp(variableName)}\b(?:\s*:[^=;]+)?\s*=`, "g"), new RegExp(String.raw`\b${escapeRegExp(variableName)}\b\s*=`, "g") ]; for (const initializerPattern of initializerPatterns) { for (const match of structure.matchAll(initializerPattern)) { if (typeof match.index !== "number" || match.index >= beforeIndex) continue; const variableOffset = match[0].indexOf(variableName); const variableIndex = match.index + variableOffset; if (initializerPattern !== initializerPatterns[0] && isMemberIdentifierReference(structure, variableIndex)) continue; if (initializerPattern !== initializerPatterns[0] && /\b(?:const|let|var)\s*$/.test(structure.slice(Math.max(0, match.index - 16), variableIndex))) { continue; } const equalsIndex = structure.indexOf("=", match.index + match[0].lastIndexOf("=")); if (equalsIndex === -1 || equalsIndex >= beforeIndex) continue; if ("=>"].includes(structure[equalsIndex + 1] ?? "")) continue; // 静态 key alias 既支持声明初始化,也支持调用点前的简单后续赋值。 const initializerStart = skipWhitespaceForward(structure, equalsIndex + 1); const initializerEnd = findStatementEnd(structure, initializerStart); values.push( ...staticJavaScriptExpressionValues( content, structure, content.slice(initializerStart, initializerEnd), initializerStart, beforeIndex, nestedSeenVariables, depth ) ); } } return values; } function hasStateTransitionVariableImport(content) { const structure = maskStringLiterals(maskCommentsOutsideStringLiterals(content)); const dynamicImportPattern = /\b(?:import|require)\s*\(/g; for (const match of structure.matchAll(dynamicImportPattern)) { if (typeof match.index !== "number") continue; const openParenIndex = structure.indexOf("(", match.index); const closeParenIndex = openParenIndex === -1 ? -1 : findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; const argumentStart = skipWhitespaceForward(structure, openParenIndex + 1); const argumentEnd = skipWhitespaceBackward(structure, closeParenIndex - 1) + 1; if (argumentStart >= argumentEnd) continue; // 动态 import/require 的实参统一复用静态 JS 表达式折叠,覆盖 identifier、递归 alias 与直接字符串拼接。 if ( staticJavaScriptExpressionValues( content, structure, content.slice(argumentStart, argumentEnd), argumentStart, match.index, new Set(), 0 ).some(hasStateTransitionSpecifier) ) { return true; } } return hasStateTransitionRequireAliasImport(content, structure); } function findRequireAliases(structure) { const aliases = []; const destructuringPattern = /\b(?:const|let|var)\s*\{\s*require\s*:\s*([A-Za-z_$][\w$]*)\s*\}\s*=\s*module\b/g; const patterns = [ /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b(?:\s*:[^=;]+)?\s*=\s*require\b/g, /\b([A-Za-z_$][\w$]*)\b\s*=\s*require\b/g ]; for (const match of structure.matchAll(destructuringPattern)) { if (typeof match.index !== "number") continue; const moduleIndex = match.index + match[0].lastIndexOf("module"); const afterModule = skipWhitespaceForward(structure, moduleIndex + "module".length); const statementEnd = findStatementEnd(structure, moduleIndex); if (afterModule !== statementEnd && !hasLineTerminator(structure.slice(afterModule, statementEnd))) continue; // CommonJS module.require 的解构 alias 只接受简单同文件声明。 aliases.push({ name: match[1], index: match.index, end: afterModule }); } for (const pattern of patterns) { for (const match of structure.matchAll(pattern)) { if (typeof match.index !== "number") continue; if (isMemberIdentifierReference(structure, match.index + match[0].indexOf(match[1]))) continue; const requireIndex = match.index + match[0].lastIndexOf("require"); const afterRequire = skipWhitespaceForward(structure, requireIndex + "require".length); const statementEnd = findStatementEnd(structure, requireIndex); if (afterRequire !== statementEnd && !hasLineTerminator(structure.slice(afterRequire, statementEnd))) continue; // require alias 只跟踪简单声明/赋值,复杂初始化器不进入 import 边界判断。 aliases.push({ name: match[1], index: match.index, end: afterRequire }); } } return aliases; } function hasStateTransitionRequireAliasImport(content, structure) { for (const alias of findRequireAliases(structure)) { const aliasCallPattern = new RegExp(String.raw`\b${escapeRegExp(alias.name)}\b\s*\(`, "g"); for (const match of structure.matchAll(aliasCallPattern)) { if (typeof match.index !== "number" || match.index <= alias.end) continue; if (isMemberIdentifierReference(structure, match.index)) continue; const openParenIndex = structure.indexOf("(", match.index + match[0].length - 1); const closeParenIndex = openParenIndex === -1 ? -1 : findMatchingParen(structure, openParenIndex); if (closeParenIndex === -1) continue; const argument = nthCallArgumentSource(content, structure, openParenIndex, closeParenIndex, 0); if (!argument) continue; if ( staticJavaScriptExpressionValues(content, structure, argument.source, argument.index, match.index, new Set(), 0).some( hasStateTransitionSpecifier ) ) { return true; } } } return false; } function javaScriptStringLiteralSourcePattern() { return String.raw`(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|` + "`(?:\\\\.|[^`\\\\])*`" + String.raw`)`; } function staticModuleSpecifierValues(content) { const values = []; const structure = maskCommentsOutsideStringLiterals(content); const literalSource = javaScriptStringLiteralSourcePattern(); const patterns = [ new RegExp(String.raw`\bimport\s+(${literalSource})`, "g"), new RegExp(String.raw`\bfrom\s+(${literalSource})`, "g") ]; for (const pattern of patterns) { for (const match of structure.matchAll(pattern)) { if (typeof match.index !== "number") continue; const literalOffset = match[0].lastIndexOf(match[1]); const literalStart = match.index + literalOffset; const decodedValue = decodeStaticJavaScriptStringLiteralSource(content.slice(literalStart, literalStart + match[1].length)); if (decodedValue !== null) values.push(decodedValue); } } return values; } function hasStaticStateTransitionSpecifier(content) { return staticModuleSpecifierValues(content).some(hasStateTransitionSpecifier); } function hasStateTransitionServiceImport(content) { // state-transition 只能由白名单路径直接耦合;静态、动态与 CommonJS 字符串入口都要按同一路径语义拦截。 return ( hasStaticStateTransitionSpecifier(content) || /\bimport\s*\(\s*(?:["'][^"']*state-transition(?:\/index\.js)?["']|`[^`]*state-transition(?:\/index\.js)?`)\s*\)/.test( content ) || /\brequire\s*\(\s*(?:["'][^"']*state-transition(?:\/index\.js)?["']|`[^`]*state-transition(?:\/index\.js)?`)\s*\)/.test( content ) || hasStateTransitionVariableImport(content) || /StateTransitionService/.test(content) ); } function isAllowedStateTransitionImportPath(filePath) { // jobs 测试需要 StateTransitionService 创建受 guard 保护的版本夹具;这不是生产调用入口。 if (stateTransitionImportFixtureTests.has(filePath)) return true; if (allowedStateTransitionImportPaths.has(filePath)) return true; return allowedStateTransitionImportPrefixes.some((prefix) => filePath.startsWith(prefix)); } function isAllowedS1AnchorPath(filePath) { return ( filePath.startsWith("apps/api/prisma/") || filePath.startsWith(stateTransitionModulePrefix) || filePath.startsWith(auditModulePrefix) || filePath === historicalPrismaSchemaTest ); } function hasPrismaModelMethodCallExcluding(content, models, methods, excludeCallBody) { const scanContent = stripStringLiteralsExceptPropertyKeys(maskCommentsOutsideStringLiterals(content)); for (const match of prismaModelMethodCallMatchesInStructure(content, scanContent, models, methods)) { const openParenIndex = scanContent[match.callStart] === "(" ? match.callStart : scanContent.indexOf("(", match.callStart); if (openParenIndex === -1) return true; const closeParenIndex = findMatchingParen(scanContent, openParenIndex); if (closeParenIndex === -1) return true; const callBody = scanContent.slice(openParenIndex, closeParenIndex + 1); if (!excludeCallBody(callBody)) return true; } return false; } function isSimpleIdentifierKey(structure, partStart, partEnd, expectedKey) { const keyStart = skipWhitespaceForward(structure, partStart); const keyEnd = destructuringPropertyKeyEnd(structure, keyStart, partEnd); if (keyEnd === -1) return false; if (structure.slice(keyStart, keyEnd).trim() !== expectedKey) return false; return structure[skipWhitespaceForward(structure, keyEnd)] === ":"; } function objectPropertyValueSpan(structure, partStart, partEnd) { const keyStart = skipWhitespaceForward(structure, partStart); const keyEnd = destructuringPropertyKeyEnd(structure, keyStart, partEnd); if (keyEnd === -1) return null; const colonIndex = skipWhitespaceForward(structure, keyEnd); if (structure[colonIndex] !== ":") return null; const valueStart = skipWhitespaceForward(structure, colonIndex + 1); const valueEnd = skipWhitespaceBackward(structure, partEnd - 1) + 1; return valueStart < valueEnd ? { start: valueStart, end: valueEnd } : null; } function soleObjectPropertyValueSpan(structure, objectStart, objectEnd, expectedKey) { if (structure[objectStart] !== "{" || objectEnd <= objectStart) return null; const closeBraceIndex = findMatchingBrace(structure, objectStart); if (closeBraceIndex === -1 || closeBraceIndex !== objectEnd - 1) return null; const parts = destructuringParts(structure, objectStart + 1, closeBraceIndex).filter( (part) => skipWhitespaceForward(structure, part.start) < skipWhitespaceBackward(structure, part.end - 1) + 1 ); if (parts.length !== 1) return null; const [part] = parts; if (!isSimpleIdentifierKey(structure, part.start, part.end, expectedKey)) return null; return objectPropertyValueSpan(structure, part.start, part.end); } function isRunIdStartsWithValue(structure, valueStart, valueEnd) { const value = structure.slice(valueStart, valueEnd).trim(); return value === "runId"; } function isAllowedJobDeleteCleanupCallBody(filePath, callBody) { if (!isTestFile(filePath)) return false; const callStart = skipWhitespaceForward(callBody, 0); if (callBody[callStart] !== "(") return false; const callEnd = findMatchingParen(callBody, callStart); if (callEnd === -1 || skipWhitespaceForward(callBody, callEnd + 1) !== callBody.length) return false; const argument = nthCallArgumentSource(callBody, callBody, callStart, callEnd, 0); if (!argument) return false; if (nthCallArgumentSource(callBody, callBody, callStart, callEnd, 1)) return false; const argumentStart = skipWhitespaceForward(callBody, argument.index); const argumentEnd = skipWhitespaceBackward(callBody, argument.end - 1) + 1; // 测试清理只能是 deleteMany({ where: { id: { startsWith: runId } } });任何 OR/AND/NOT、额外 key、spread/computed 都 fail closed。 const whereValue = soleObjectPropertyValueSpan(callBody, argumentStart, argumentEnd, "where"); if (!whereValue) return false; const whereStart = skipWhitespaceForward(callBody, whereValue.start); const whereEnd = skipWhitespaceBackward(callBody, whereValue.end - 1) + 1; const idValue = soleObjectPropertyValueSpan(callBody, whereStart, whereEnd, "id"); if (!idValue) return false; const idStart = skipWhitespaceForward(callBody, idValue.start); const idEnd = skipWhitespaceBackward(callBody, idValue.end - 1) + 1; const startsWithValue = soleObjectPropertyValueSpan(callBody, idStart, idEnd, "startsWith"); if (!startsWithValue) return false; return isRunIdStartsWithValue(callBody, startsWithValue.start, startsWithValue.end); } function hasForbiddenJobPrismaDelete(filePath, content) { // 单行 delete 无法表达 runId 前缀清理语义,始终视为 Job 运行态删除;只有 deleteMany 可走测试清理白名单。 return ( prismaModelMethodCallMatches(content, ["job"], ["delete"]).length > 0 || hasPrismaModelMethodCallExcluding(content, ["job"], ["deleteMany"], (callBody) => isAllowedJobDeleteCleanupCallBody(filePath, callBody)) ); } function hasJobStatusWrite(filePath, content) { // Job 创建路径同样会写入运行态字段,S1 必须统一收敛到 JobExecutionStore。 return ( hasPrismaModelMethodCallContaining(content, ["job"], jobPrismaWriteMethods, { test: hasForbiddenJobWriteCallBody }) || hasForbiddenJobPrismaDelete(filePath, content) || hasRawJobStatusWrite(content) ); } function hasForbiddenJobStatusWrite(filePath, content) { // SQL 文件走原始 SQL 扫描;源码文件继续复用 Prisma 与 raw 调用扫描。 if (filePath.endsWith(".sql")) return hasForbiddenRawJobStatusWrite(filePath, content); return hasJobStatusWrite(filePath, content); } function isAllowedJobExecutionStorePath(filePath) { return filePath === jobExecutionStorePath; } async function collectFiles(root, relativeDir) { const absoluteDir = path.join(root, relativeDir); const entries = await readdir(absoluteDir, { withFileTypes: true }).catch((error) => { if (error && error.code === "ENOENT") return []; throw error; }); const files = []; for (const entry of entries) { const relativePath = normalizePath(path.join(relativeDir, entry.name)); if (entry.isDirectory()) { if (ignoredScanSegments.has(entry.name)) continue; files.push(...(await collectFiles(root, relativePath))); continue; } if (entry.isFile() && sourceLikeFilePattern.test(entry.name) && !isIgnoredScanPath(relativePath)) { files.push({ path: relativePath, content: await readFile(path.join(root, relativePath), "utf8") }); } } return files; } async function collectWorkspaceFiles(root) { const files = []; for (const scanRoot of scanRoots) { files.push(...(await collectFiles(root, scanRoot))); } return files; } function pushViolation(violations, filePath, reason, detail) { violations.push({ path: filePath, reason, detail }); } function normalizedPathSegments(filePath) { return normalizePath(filePath) .replace(/^\/+|\/+$/g, "") .split("/") .filter((segment) => segment.length > 0); } function hasS2ApprovedTermDenyPath(filePath) { const normalizedFilePath = `/${normalizePath(filePath).replace(/^\/+|\/+$/g, "")}/`; if (s2ApprovedTermDenyPathFragments.some((fragment) => normalizedFilePath.includes(fragment))) return true; return normalizedPathSegments(filePath).some((segment) => s2ApprovedTermDenyPathSegments.has(segment)); } function hasS2ApprovedTermAllowPath(filePath) { if (s2ApprovedTermAllowExactPaths.has(filePath)) return true; return s2ApprovedTermAllowPathPrefixes.some((prefix) => filePath.startsWith(prefix)); } function isAllowedS2LegacyS3TermReference(filePath, term) { return s2LegacyS3TermAllowExactPaths.get(term)?.has(filePath) ?? false; } function isAllowedS2ApprovedTermReference(filePath, term) { if (isAllowedS2LegacyS3TermReference(filePath, term)) return true; if (!s2ApprovedTerms.has(term)) return false; if (hasS2ApprovedTermDenyPath(filePath)) return false; // S2 approved 术语只能出现在 Creator Workbench、AI design、GameIR 和共享合同边界内。 // 不从 denylist 全局删除这些词,避免 simulation/runtime/conversion/feed 等后续阶段路径借名绕过门禁。 return hasS2ApprovedTermAllowPath(filePath); } function hasS3ApprovedTermAllowPath(filePath) { if (s3ApprovedTermAllowExactPaths.has(filePath)) return true; return s3ApprovedTermAllowPathPrefixes.some((prefix) => filePath.startsWith(prefix)); } function isAllowedS3ApprovedTermReference(filePath, term) { if (isAllowedS2ApprovedTermReference(filePath, term)) return true; if (s3InheritedS2Terms.has(term) && hasS3ApprovedTermAllowPath(filePath)) return true; // S3 只允许在 simulation slice 自有 API 模块和共享合同边界使用这些术语。 // 既有 S2 GameIR 合同可继续描述 simulation payload/NPC graph,但不能借此开放 S3 新模块。 return s3ApprovedTerms.has(term) && hasS3ApprovedTermAllowPath(filePath); } function shouldSkipLaterPhaseTermViolation(filePath, term, phase) { if (phase === "S2" && isAllowedS2ApprovedTermReference(filePath, term)) return true; if (phase === "S1") return false; if (phase === "S2") return false; if (phase === "S3") return isAllowedS3ApprovedTermReference(filePath, term); throw new Error(`Unsupported scope phase: ${phase}`); } function findS3OwnedPathScopeViolations(file, violations, phase) { if (phase === "S3") return; const normalizedFilePath = normalizePath(file.path); if (s3OwnedPathPrefixes.some((prefix) => normalizedFilePath.startsWith(prefix))) { pushViolation(violations, file.path, "LATER_PHASE_SCOPE_LEAK", "S3-owned path"); } } function findLaterPhaseScopeViolations(file, violations, phase) { for (const term of laterPhaseDeniedTerms) { // 路径名本身不能提供 validateContract 调用上下文;harness-client 测试也不能按路径泛化放行。 if (pathContainsLaterPhaseTerm(file.path, term)) { if (shouldSkipLaterPhaseTermViolation(file.path, term, phase)) continue; pushViolation(violations, file.path, "LATER_PHASE_SCOPE_LEAK", term); continue; } let hasContentViolation = false; for (const contentTerm of contentDeniedTermVariants(term)) { let index = file.content.indexOf(contentTerm); while (index !== -1) { if (!isAllowedHarnessValidationContractReferenceAt(file.path, file.content, contentTerm, index)) { if (shouldSkipLaterPhaseTermViolation(file.path, term, phase)) break; pushViolation(violations, file.path, "LATER_PHASE_SCOPE_LEAK", term); hasContentViolation = true; break; } index = file.content.indexOf(contentTerm, index + contentTerm.length); } if (hasContentViolation) break; } } } function findHarnessClientContractReferenceViolations(file, violations) { if (!isHarnessClientTestPath(file.path)) return; for (const term of harnessClientContractWatchTerms) { let index = file.content.indexOf(term); while (index !== -1) { if (!isAllowedHarnessClientContractReferenceAt(file.path, file.content, term, index)) { pushViolation(violations, file.path, "HARNESS_CONTRACT_REFERENCE_OUTSIDE_VALIDATION", term); break; } index = file.content.indexOf(term, index + term.length); } } } function s1AnchorTermsForPhase(phase) { if (phase === "S1") return s1AnchorTerms; if (phase === "S2" || phase === "S3") { // S2/S3 会启用 S1 已存在的创作 Agent anchor;审核/生命周期 anchor 仍不能进入后续业务实现。 return s1AnchorTerms.filter((term) => !s2ApprovedS1AnchorTerms.has(term)); } throw new Error(`Unsupported scope phase: ${phase}`); } function identifierAt(content, index) { const left = content.slice(0, index).match(/[A-Za-z_$][\w$]*$/)?.[0] ?? ""; const right = content.slice(index).match(/^[A-Za-z_$][\w$]*/)?.[0] ?? ""; return `${left}${right}`; } function isAllowedS1AnchorReferenceAt(filePath, content, term, index, phase) { if ( phase === "S3" && term === "LifecycleEvent" && s3RuntimeSdkLifecycleDtoAllowExactPaths.has(filePath) ) { const identifier = identifierAt(content, index); // S3 RuntimeSdkContract 需要公开平台中立 LifecycleEventDto;只精确放行 DTO 名称, // 不能把 S1 持久化 LifecycleEvent anchor 泛化开放给业务代码。 return identifier === "LifecycleEventDto" || identifier === "validateLifecycleEventDto"; } return false; } function findS1AnchorScopeViolations(file, violations, phase) { if (isAllowedS1AnchorPath(file.path)) return; const normalizedFilePath = normalizePath(file.path); for (const term of s1AnchorTermsForPhase(phase)) { let index = file.content.indexOf(term); while (index !== -1) { if (!isAllowedS1AnchorReferenceAt(normalizedFilePath, file.content, term, index, phase)) { pushViolation(violations, file.path, "S1_ANCHOR_SCOPE_MISUSE", term); break; } index = file.content.indexOf(term, index + term.length); } } } function findAuditBoundaryViolations(file, violations) { if (isGeneratedPrismaFile(file.path)) return; if (hasForbiddenAuditMutation(file.path, file.content)) { pushViolation(violations, file.path, "AUDIT_UPDATE_DELETE_FORBIDDEN", "auditLog update/delete/upsert"); } } function findDynamicRawSqlViolations(file, violations) { if (isGeneratedPrismaFile(file.path)) return; if (hasDynamicIdentifierRawSqlWrite(file.content)) { pushViolation(violations, file.path, "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", "dynamic raw SQL write identifier"); } } function findStateBoundaryViolations(file, violations) { if (isHistoricalTestFile(file.path) || isGeneratedPrismaFile(file.path)) return; if (file.path.startsWith("apps/worker/")) { if (/@prisma\/client|generated\/prisma|PrismaClient/.test(file.content)) { pushViolation(violations, file.path, "WORKER_DB_IMPORT_FORBIDDEN", "Prisma import in worker"); } if (/state-transition|StateTransitionService|app\.state_transition_guard|set_config\s*\(|SET\s+LOCAL/i.test(file.content)) { pushViolation(violations, file.path, "WORKER_STATE_TRANSITION_IMPORT_FORBIDDEN", "state transition coupling in worker"); } return; } if (hasStateTransitionServiceImport(file.content) && !isAllowedStateTransitionImportPath(file.path)) { pushViolation(violations, file.path, "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", "state-transition service import"); } if (file.path.startsWith(stateTransitionModulePrefix)) return; if (file.path.startsWith(prismaMigrationPrefix)) { // migration 允许安装 trigger/current_setting guard,但不允许写运行时 guard setter。 if (hasForbiddenMigrationGuardSetter(file.content)) { pushViolation(violations, file.path, "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", "migration runtime guard setter"); } if (hasGuardedPrismaWrite(file.path, file.content)) { pushViolation(violations, file.path, "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", "migration guarded model write"); } return; } if (hasGuardedPrismaWrite(file.path, file.content)) { pushViolation(violations, file.path, "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", "guarded model write"); } if (hasRuntimeGuardSetter(file.content)) { pushViolation(violations, file.path, "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", "runtime guard setter"); } } function findJobExecutionBoundaryViolations(file, violations) { const scanContent = isAllowedJobExecutionStorePath(file.path) ? maskJobExecutionStoreClassBodies(file.content) : file.content; if (hasForbiddenJobStatusWrite(file.path, scanContent)) { pushViolation(violations, file.path, "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", "Job status write"); } } function findViolationsInFiles(files) { return findScopeViolationsInFiles(files, "S1"); } function findScopeViolationsInFiles(files, phase) { const violations = []; for (const file of files) { if (isIgnoredScanPath(file.path) || isGeneratedPrismaFile(file.path)) continue; findS3OwnedPathScopeViolations(file, violations, phase); findLaterPhaseScopeViolations(file, violations, phase); findHarnessClientContractReferenceViolations(file, violations); findS1AnchorScopeViolations(file, violations, phase); findAuditBoundaryViolations(file, violations); findStateBoundaryViolations(file, violations); findDynamicRawSqlViolations(file, violations); findJobExecutionBoundaryViolations(file, violations); } return violations; } async function findS1ScopeViolations(root) { return findScopeViolations(root, "S1"); } async function findScopeViolations(root, phase) { return findScopeViolationsInFiles(await collectWorkspaceFiles(root), phase); } async function withFixture(files, callback) { const root = await mkdtemp(path.join(os.tmpdir(), "huijing-s1-scope-")); try { for (const [relativePath, content] of Object.entries(files)) { const absolutePath = path.join(root, relativePath); await mkdir(path.dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, content); } return await callback(root); } finally { await rm(root, { recursive: true, force: true }); } } function assertHasReason(violations, reason, label) { if (!violations.some((violation) => violation.reason === reason)) { throw new Error(`${label}: expected ${reason}`); } } function assertNoViolations(violations, label) { if (violations.length > 0) { const details = violations.map(formatViolation).join("; "); throw new Error(`${label}: expected no violations, got ${details}`); } } function assertThrows(fn, label) { try { fn(); } catch { return; } throw new Error(`${label}: expected throw`); } function formatViolation(violation) { return `${violation.path}: ${violation.reason}${violation.detail ? ` (${violation.detail})` : ""}`; } function parsePhaseArg(argv) { const phaseFlagIndexes = argv.flatMap((arg, index) => (arg === "--phase" ? [index] : [])); if (phaseFlagIndexes.length !== 1) { throw new Error("Expected exactly one --phase S1|S2|S3"); } const [phaseFlagIndex] = phaseFlagIndexes; if (phaseFlagIndex === argv.length - 1) { throw new Error("Missing required --phase S1|S2|S3"); } const phase = argv[phaseFlagIndex + 1]?.toUpperCase(); if (!supportedPhases.has(phase)) { throw new Error(`Unsupported --phase value: ${argv[phaseFlagIndex + 1] ?? ""}`); } return phase; } async function runSelfTests() { assertThrows(() => parsePhaseArg(["--phase", "S2", "--phase", "S1"]), "duplicate phase flag"); if (parsePhaseArg(["--phase", "S3"]) !== "S3") { throw new Error("S3 phase parsing: expected S3"); } await withFixture( { "apps/api/src/modules/creator/index.ts": 'export const route = "creation-agent/messages";' }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "later-phase app route"); } ); await withFixture( { "packages/harness-client/src/index.spec.ts": 'createHarnessClient().validateContract("MiniGameCodeConversion", { id: "fixture" });' }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "harness-client S0 contract fixture reference"); } ); await withFixture( { "packages/harness-client/src/probe.spec.ts": 'createHarnessClient(); const businessModelName = "MiniGameProject";' }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "harness-client non-validation S0 contract probe"); } ); await withFixture( { "packages/harness-client/src/bad-game-ir.spec.ts": 'const contractName = "GameIR";' }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "harness-client non-validation GameIR contract name"); } ); await withFixture( { "packages/harness-client/src/bad-validation-report.spec.ts": 'const contractName = "ValidationReport";' }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason( violations, "HARNESS_CONTRACT_REFERENCE_OUTSIDE_VALIDATION", "harness-client non-validation ValidationReport contract name" ); } ); await withFixture( { "packages/harness-client/src/index.spec.ts": 'createHarnessClient().validateContract("GameIR", { id: "fixture" });' }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "harness-client GameIR validation reference"); } ); await withFixture( { "packages/harness-client/src/index.spec.ts": 'createHarnessClient().validateContract("ValidationReport", { id: "fixture" });' }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "harness-client ValidationReport validation reference"); } ); await withFixture( { "packages/harness-client/src/index.spec.ts": 'expect(args[2]).toBe("GameIR");' }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "isolated harness-client args[2] contract assertion"); } ); await withFixture( { "packages/harness-client/src/index.spec.ts": ` const runner = async (_file, args) => { expect(args.slice(0, 2)).toEqual([realHarnessCli, "--contract"]); expect(args[2]).toBe("GameIR"); }; ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "harness-client args[2] assertion with runner contract context"); } ); const harnessClientPathOnlyLaterPhaseFixtures = [ "packages/harness-client/src/game-ir.spec.ts", "packages/harness-client/src/game-ir/index.spec.ts", "packages/harness-client/src/game-ir-artifact.spec.ts", "packages/harness-client/src/validation-report.spec.ts" ]; for (const filePath of harnessClientPathOnlyLaterPhaseFixtures) { await withFixture( { [filePath]: "export const harmless = true;" }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", `harness-client path-only later-phase leak ${filePath}`); } ); } await withFixture( { "apps/api/src/modules/projects/index.ts": 'const contractName = "MiniGameCodeConversion";' }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "API business later-phase contract"); } ); await withFixture( { "apps/api/src/modules/projects/agent.ts": "export class MainCreationAgentSessionController {}" }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "S1_ANCHOR_SCOPE_MISUSE", "S1 anchor business misuse"); } ); await withFixture( { "apps/api/prisma/migrations/20260601000000_guard/migration.sql": ` CREATE OR REPLACE FUNCTION app_require_state_transition_guard() RETURNS trigger AS $$ BEGIN IF current_setting('app.state_transition_guard', true) IS DISTINCT FROM 'on' THEN RAISE EXCEPTION 'guard required'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER "LifecycleEvent_a_guard_insert" BEFORE INSERT ON "LifecycleEvent" FOR EACH ROW EXECUTE FUNCTION app_require_state_transition_guard(); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "migration trigger/current_setting guard"); } ); await withFixture( { "apps/api/prisma/migrations/20260602010101_install_audit_guard/migration.sql": ` CREATE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RAISE EXCEPTION 'AuditLog is append-only' USING ERRCODE = '42501'; END; $$ LANGUAGE plpgsql; ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "plain AuditLog append-only function install migration"); } ); const failingFixtures = [ { label: "migration set_config guard setter", reason: "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", files: { "apps/api/prisma/migrations/20260601000001_bad_guard/migration.sql": "SELECT set_config('app.state_transition_guard', 'on', true);" } }, { label: "app SET LOCAL guard setter", reason: "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/bad.ts": 'await db.$executeRawUnsafe("SET LOCAL app.state_transition_guard = on");' } }, { label: "app raw SQL guard setter", reason: "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/raw.ts": 'await db.$executeRawUnsafe("SELECT set_config(\'app.state_transition_guard\', \'on\', true)");' } }, { label: "API spec raw SQL guard setter", reason: "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/bad.spec.ts": 'await db.$executeRawUnsafe("SELECT set_config(\'app.state_transition_guard\', \'on\', true)");' } }, { label: "API spec SET LOCAL guard setter", reason: "STATE_GUARD_SETTER_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/bad-set-local.spec.ts": 'await db.$executeRawUnsafe("SET LOCAL app.state_transition_guard = on");' } }, { label: "audit update/delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit.ts": "await db.auditLog.update({ where: { id }, data: {} }); await db.auditLog.delete({ where: { id } });" } }, { label: "audit bracket model update outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-bracket-model.ts": 'await db["auditLog"].update({ where: { id }, data: {} });' } }, { label: "audit computed bracket model delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-computed-bracket-model.ts": 'await db["audit" + "Log"].delete({ where: { id } });' } }, { label: "audit bracket method delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-bracket-method.ts": 'await db.auditLog["delete"]({ where: { id } });' } }, { label: "audit bracket model and method delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-bracket-model-method.ts": 'await db["auditLog"]["delete"]({ where: { id } });' } }, { label: "audit optional delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-optional-delete.ts": "await db.auditLog?.delete({ where: { id } });" } }, { label: "audit optional bracket delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-optional-bracket-delete.ts": 'await db["auditLog"]?.["delete"]({ where: { id } });' } }, { label: "audit parenthesized delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-parenthesized-delete.ts": "await (db.auditLog).delete({ where: { id } });" } }, { label: "audit delegate alias delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alias-delete.ts": "const logs = db.auditLog; await logs.delete({ where: { id } });" } }, { label: "audit destructured delegate alias delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-destructured-alias-delete.ts": "const { auditLog: logs } = db; await logs.delete({ where: { id } });" } }, { label: "audit quoted destructured delegate alias delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-quoted-destructured-alias-delete.ts": 'const { "auditLog": logs } = db; await logs.delete({ where: { id } });' } }, { label: "audit assigned delegate alias delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-assigned-alias-delete.ts": ` let logs; logs = db.auditLog; await logs.delete({ where: { id } }); ` } }, { label: "audit Function.prototype.call delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-function-call-delete.ts": "Function.prototype.call.call(db.auditLog.delete, db.auditLog, { where: { id } });" } }, { label: "audit Function.prototype.call alias delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-function-call-alias-delete.ts": ` const c = Function.prototype.call; c.call(db.auditLog.delete, db.auditLog, { where: { id } }); ` } }, { label: "audit update/delete in API spec outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit.spec.ts": "await db.auditLog.update({ where: { id }, data: {} });" } }, { label: "audit update/delete inside audit module implementation", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/audit/bad.ts": "await db.auditLog.update({ where: { id }, data: {} }); await db.auditLog.delete({ where: { id } });" } }, { label: "state-transition service import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state.ts": 'import { StateTransitionService } from "../state-transition/index.js";' } }, { label: "state-transition side-effect import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-side-effect.ts": 'import "../state-transition/index.js";' } }, { label: "state-transition escaped side-effect import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-side-effect-escaped.ts": 'import "../state-\\u0074ransition/index.js";' } }, { label: "state-transition escaped named import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-named-escaped.ts": 'import { StateTransitionService } from "../state-\\u0074ransition/index.js";' } }, { label: "state-transition escaped export from outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-export-escaped.ts": 'export { StateTransitionService } from "../state-\\u0074ransition/index.js";' } }, { label: "state-transition dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic.ts": 'await import("../state-transition/index.js");' } }, { label: "state-transition backtick dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-backtick.ts": 'await import(`../state-transition/index.js`);' } }, { label: "state-transition parenthesized literal dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-parenthesized-literal.ts": 'await import(("../state-transition/index.js"));' } }, { label: "state-transition parenthesized backtick dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-parenthesized-backtick.ts": "await import((`../state-transition/index.js`));" } }, { label: "state-transition concatenated direct dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-concatenated-direct.ts": 'await import("../state-transition" + "/index.js");' } }, { label: "state-transition parenthesized concatenated direct dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-parenthesized-concatenated-direct.ts": 'await import(("../state-transition" + "/index.js"));' } }, { label: "state-transition require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require.ts": 'require("../state-transition/index.js");' } }, { label: "state-transition parenthesized literal require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-parenthesized-literal.ts": 'require(("../state-transition/index.js"));' } }, { label: "state-transition concatenated direct require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-concatenated-direct.ts": 'require("../state-" + "transition/index.js");' } }, { label: "state-transition parenthesized concatenated direct require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-parenthesized-concatenated-direct.ts": 'require(("../state-" + "transition/index.js"));' } }, { label: "state-transition variable dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-variable.ts": 'const mod = "../state-transition/index.js"; await import(mod);' } }, { label: "state-transition recursive variable dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-dynamic-recursive-variable.ts": 'const stateTransitionPath = "../state-transition/index.js"; const mod = stateTransitionPath; await import(mod);' } }, { label: "state-transition variable require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-variable.ts": 'const mod = "../state-transition/index.js"; require(mod);' } }, { label: "state-transition concatenated variable require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-concatenated-variable.ts": 'const base = "../state-transition"; const mod = base + "/index.js"; require(mod);' } }, { label: "state-transition concatenated variable dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/projects/state-dynamic-concatenated-variable.ts": 'const base = "../state-"; await import(base + "transition/index.js");' } }, { label: "state-transition concatenated variable direct require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/projects/state-require-direct-concatenated-variable.ts": 'const base = "../state-"; require(base + "transition/index.js");' } }, { label: "state-transition static template expression dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/projects/state-dynamic-static-template.ts": 'await import(`../${"state-transition"}/index.js`);' } }, { label: "state-transition static template variable dynamic import outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/projects/state-dynamic-static-template-variable.ts": 'const seg = "state-transition"; await import(`../${seg}/index.js`);' } }, { label: "state-transition destructured require alias outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/projects/state-destructured-require-alias.ts": 'const { require: load } = module; load("../state-transition/index.js");' } }, { label: "state-transition backtick require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-backtick.ts": 'require(`../state-transition/index.js`);' } }, { label: "state-transition aliased require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-alias.ts": 'const r = require; return r("../state-transition/index.js");' } }, { label: "state-transition assigned aliased require outside allowed path", reason: "STATE_TRANSITION_SERVICE_IMPORT_FORBIDDEN", files: { "apps/api/src/modules/assets/state-require-assigned-alias.ts": ` let r; r = require; r("../state-transition/index.js"); ` } }, { label: "Prisma import in worker", reason: "WORKER_DB_IMPORT_FORBIDDEN", files: { "apps/worker/src/index.ts": 'import { PrismaClient } from "@prisma/client";' } }, { label: "worker state-transition import", reason: "WORKER_STATE_TRANSITION_IMPORT_FORBIDDEN", files: { "apps/worker/src/state.ts": 'import { StateTransitionService } from "../../api/src/modules/state-transition/index.js";' } }, { label: "direct Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job.ts": "await db.job.update({ where: { id }, data: { status: 'failed' } });" } }, { label: "direct Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-delete.ts": "await db.job.delete({ where: { id } });" } }, { label: "direct Job deleteMany outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-delete-many.ts": 'await db.job.deleteMany({ where: { status: "running" } });' } }, { label: "test Job delete runId cleanup outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-cleanup.spec.ts": ` const runId = "scope-test"; await db.job.delete({ where: { id: { startsWith: runId } } }); ` } }, { label: "test Job deleteMany OR cleanup outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-cleanup.spec.ts": ` const runId = "scope-test"; await db.job.deleteMany({ where: { OR: [{ id: { startsWith: runId } }, { status: "running" }] } }); ` } }, { label: "direct Job bracket model status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bracket-model.ts": 'await db["job"].update({ where: { id }, data: { status: "failed" } });' } }, { label: "direct Job bracket method status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bracket-method.ts": 'await db.job["update"]({ where: { id }, data: { status: "failed" } });' } }, { label: "direct Job bracket model and method status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bracket-model-method.ts": 'await db["job"]["update"]({ where: { id }, data: { status: "failed" } });' } }, { label: "direct Job optional status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-optional-update.ts": "await db.job?.update({ where: { id }, data: { status } });" } }, { label: "direct Job optional bracket status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-optional-bracket-update.ts": 'await db["job"]?.["update"]({ where: { id }, data: { status } });' } }, { label: "direct Job parenthesized delegate status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-parenthesized-update.ts": 'await (db.job).update({ where: { id }, data: { status: "failed" } });' } }, { label: "direct Job delegate alias status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-alias-update.ts": "const jobs = db.job; await jobs.update({ where: { id }, data: { status } });" } }, { label: "direct Job delegate alias delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-alias-delete.ts": "const jobs = db.job; await jobs.delete({ where: { id } });" } }, { label: "direct Job destructured delegate alias status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-destructured-alias-update.ts": 'const { job } = db; await job.update({ where: { id }, data: { status: "failed" } });' } }, { label: "direct Job assigned delegate alias status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-assigned-alias-update.ts": ` let jobs; jobs = db.job; await jobs.update({ where: { id }, data: { status: "running" } }); ` } }, { label: "direct Job status write with comment paren outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-comment-paren.ts": ` await db.job.update({ // ) where: { id }, data: { status: "failed" } }); ` } }, { label: "direct Job status write with data identifier outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-data-identifier.ts": 'const patch = { status: "failed" }; await db.job.update({ where: { id }, data: patch });' } }, { label: "direct Job status write with quoted keys outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-quoted.ts": 'await db.job.update({ where: { id }, data: { "status": "failed", \'attempts\': 1, [`nextRetryAt`]: null } });' } }, { label: "direct Job lease token write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-lease-token.ts": 'await db.job.update({ where: { id }, data: { leaseToken: "stolen" } });' } }, { label: "direct Job timeoutAt write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-timeout-at.ts": 'await db.job.update({ where: { id }, data: { timeoutAt: new Date() } });' } }, { label: "direct Job runtime write with dynamic computed key outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-computed.ts": 'const field = "status"; await db.job.update({ where: { id }, data: { [field]: "failed" } });' } }, { label: "direct Job status create outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-create.ts": 'await db.job.create({ data: { projectId, actorId, status: "queued", attempts: 0, nextRetryAt: null } });' } }, { label: "variableized Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-variable-data.ts": 'const data = { status: "failed" }; await db.job.update({ where: { id }, data });' } }, { label: "spread Job data write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-spread-data.ts": 'const patch = { attempts: 1 }; await db.job.update({ where: { id }, data: { ...patch } });' } }, { label: "raw SQL Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-status.ts": `await db.$executeRawUnsafe("UPDATE \\"Job\\" SET \\"status\\" = 'failed' WHERE id = $1", id);` } }, { label: "raw SQL Job identity-escaped keyword status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-identity-escaped-keyword-status.ts": 'await db.$executeRawUnsafe("\\U\\P\\D\\A\\T\\E \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "raw SQL Job unicode-escaped table status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-unicode-table.ts": 'await db.$executeRawUnsafe("UPDATE \\"\\u004aob\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "raw SQL Job unicode quoted identifier status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-unicode-quoted-identifier-status.ts": `await db.$executeRawUnsafe('UPDATE U&"J\\\\006Fb" SET U&"stat\\\\0075s" = $1 WHERE id = $2', status, id);` } }, { label: "raw SQL Job custom UESCAPE unicode quoted identifier status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-unicode-quoted-custom-uescape-status.ts": `await db.$executeRawUnsafe('UPDATE U&"J!006Fb" UESCAPE \\'!\\' SET U&"stat!0075s" UESCAPE \\'!\\' = $1 WHERE id = $2', status, id);` } }, { label: "optional raw SQL Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-optional-status.ts": 'await db.$executeRawUnsafe?.("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "parenthesized raw SQL Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-parenthesized-status.ts": 'await (db.$executeRawUnsafe)("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "parenthesized optional bracket raw SQL Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-parenthesized-optional-bracket-status.ts": 'await (db?.["$executeRawUnsafe"])("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "bound raw SQL Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-bind-status.ts": 'await db.$executeRawUnsafe.bind(db)("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "optional bound raw SQL Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-optional-bind-status.ts": 'await db.$executeRawUnsafe?.bind(db)("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "no-semicolon bound raw SQL Job status alias outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-bound-alias-no-semicolon.ts": ` const execRaw = db.$executeRawUnsafe.bind(db) await execRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id) ` } }, { label: "no-semicolon optional bound raw SQL Job status alias outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-optional-bound-alias-no-semicolon.ts": ` const execRaw = db.$executeRawUnsafe?.bind(db) await execRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id) ` } }, { label: "raw SQL Job status write with comment paren outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-comment-paren.ts": ` await db.$executeRawUnsafe( // ) "UPDATE \\"Job\\" SET \\"status\\" = 'failed' WHERE id = $1", id ); ` } }, { label: "raw SQL Job attempts write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-attempts.ts": 'await db.$executeRawUnsafe("UPDATE Job SET attempts = attempts + 1 WHERE id = $1", id);' } }, { label: "raw SQL Job nextRetryAt write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-next-retry.ts": 'await db.$executeRawUnsafe("UPDATE \\"Job\\" SET \\"nextRetryAt\\" = NULL WHERE id = $1", id);' } }, { label: "raw SQL Job insert status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-insert-status.ts": 'await db.$executeRawUnsafe("INSERT INTO \\"Job\\" (\\"id\\", \\"status\\") VALUES ($1, \'queued\')", id);' } }, { label: "raw SQL Job lease fields update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-lease.ts": 'await db.$executeRawUnsafe("UPDATE \\"Job\\" SET \\"leaseToken\\" = $1, \\"lockVersion\\" = \\"lockVersion\\" + 1 WHERE id = $2", token, id);' } }, { label: "raw SQL Job multi-column SET outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-multi-column-set.ts": 'await db.$executeRawUnsafe("UPDATE \\"Job\\" SET (\\"status\\", \\"attempts\\") = (\'failed\', 1) WHERE id = $1", id);' } }, { label: "raw SQL Job MERGE update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-merge.ts": 'await db.$executeRawUnsafe("MERGE INTO \\"Job\\" j USING \\"JobPatch\\" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET \\"status\\" = \'failed\'");' } }, { label: "raw SQL Job ONLY MERGE update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-only-merge.ts": 'await db.$executeRawUnsafe("MERGE INTO ONLY \\"Job\\" j USING \\"JobPatch\\" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET \\"status\\" = \'failed\'");' } }, { label: "raw SQL Job ONLY block-comment MERGE update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-only-block-comment-merge.ts": 'await db.$executeRawUnsafe("MERGE/*x*/INTO ONLY \\"Job\\" j USING \\"JobPatch\\" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET \\"status\\" = \'failed\'");' } }, { label: "raw SQL Job nested block-comment update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-nested-block-comment-update.ts": 'await db.$executeRawUnsafe("UPDATE/* outer /* inner */ still outer */\\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "raw SQL Job status update after line-comment-looking string literal", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-status-after-string-line-comment.ts": `await db.$executeRawUnsafe("SELECT '-- not a comment'; UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);` } }, { label: "raw SQL Job multi-table truncate outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-multi-truncate.ts": 'await db.$executeRawUnsafe("TRUNCATE TABLE \\"Other\\", \\"Job\\"");' } }, { label: "migration SQL Job status update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000000_bad_job_update/migration.sql": 'UPDATE "Job" SET "status" = \'failed\' WHERE id = \'x\';' } }, { label: "migration SQL Job ONLY inheritance status update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000006_bad_job_only_inheritance_update/migration.sql": 'UPDATE ONLY "Job" * SET "status" = \'failed\' WHERE id = \'x\';' } }, { label: "migration SQL Job unicode quoted identifier status update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000007_bad_job_unicode_quoted_update/migration.sql": 'UPDATE U&"J\\006Fb" SET U&"stat\\0075s" = \'failed\' WHERE id = \'x\';' } }, { label: "migration SQL Job status update after line-comment-looking string literal", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000004_bad_job_update_after_string_line_comment/migration.sql": `SELECT '-- not a comment'; UPDATE "Job" SET "status" = 'failed' WHERE id = 'x';` } }, { label: "migration SQL Job MERGE update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000001_bad_job_merge/migration.sql": 'MERGE INTO "Job" j USING "JobPatch" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET "status" = \'failed\';' } }, { label: "migration SQL Job ONLY MERGE update outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000005_bad_job_only_merge/migration.sql": 'MERGE INTO ONLY "Job" j USING "JobPatch" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET "status" = \'failed\';' } }, { label: "migration SQL Job multi-table truncate outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602000003_bad_job_truncate/migration.sql": 'TRUNCATE TABLE "Other", "Job";' } }, { label: "raw SQL Job timeout insert outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-timeout.ts": 'await db.$executeRawUnsafe("INSERT INTO Job (id, timeoutAt) VALUES ($1, NOW())", id);' } }, { label: "raw SQL schema-qualified Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-schema-qualified.ts": 'await db.$executeRawUnsafe("UPDATE public.\\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "raw SQL aliased Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-alias.ts": 'await db.$executeRawUnsafe("UPDATE \\"Job\\" j SET \\"status\\" = $1 WHERE j.id = $2", status, id);' } }, { label: "raw SQL ONLY Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-only.ts": 'await db.$executeRawUnsafe("UPDATE ONLY \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "raw SQL ONLY Job inheritance status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-only-inheritance.ts": 'await db.$executeRawUnsafe("UPDATE ONLY \\"Job\\" * SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "Prisma.sql variable Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-prisma-sql-variable.ts": 'const query = Prisma.sql`UPDATE public."Job" SET "status" = ${status} WHERE id = ${id}`; await db.$executeRaw(query);' } }, { label: "concatenated SQL variable Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-concatenated-variable.ts": 'const query = "UPDATE " + "\\"Job\\"" + " SET " + "\\"status\\"" + " = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id);' } }, { label: "raw SQL Job status write through intermediate constants outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-intermediate-constants.ts": ` const tableName = '"Job"'; const fieldName = '"status"'; const query = "UPDATE " + tableName + " SET " + fieldName + " = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` } }, { label: "raw SQL Job status write through unicode-escaped intermediate constants outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-unicode-intermediate-constants.ts": ` const tableName = '"\\u004aob"'; const fieldName = '"status"'; const query = "UPDATE " + tableName + " SET " + fieldName + " = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` } }, { label: "raw SQL Job status write through dynamic table variable outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-dynamic-table.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const fieldName = '"status"'; const query = "UPDATE " + tableName + " SET " + fieldName + " = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` } }, { label: "parenthesized raw SQL dynamic write outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-parenthesized-dynamic-table.ts": ` const table = process.env.TABLE; const query = "UPDATE " + table + " SET status = $1"; await (db.$executeRawUnsafe)(query, status); ` } }, { label: "raw SQL Job ONLY inheritance write through dynamic table variable outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-dynamic-only-inheritance-table.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const query = "UPDATE ONLY " + tableName + " * j SET status = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` } }, { label: "raw SQL Job write through identity-escaped dynamic table variable outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-identity-escaped-dynamic-table.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const query = "\\U\\P\\D\\A\\T\\E " + tableName + " SET status = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` } }, { label: "raw SQL Job write through dynamic table function call outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-dynamic-table-call.ts": ` const query = "UPDATE " + resolveTable() + " SET status = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` } }, { label: "raw SQL Job write through dynamic field function call outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-dynamic-field-call.ts": ` const query = "UPDATE Job SET " + resolveField() + " = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, value, id); ` } }, { label: "raw SQL dynamic merge table outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-dynamic-merge-table.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const query = "MERGE INTO " + tableName + " j USING \\"JobPatch\\" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET status = $1"; await db.$executeRawUnsafe(query, status); ` } }, { label: "raw SQL dynamic merge only table outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-dynamic-merge-only-table.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const query = "MERGE INTO ONLY " + tableName + " j USING \\"JobPatch\\" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET status = $1"; await db.$executeRawUnsafe(query, status); ` } }, { label: "Prisma.sql Job status write through Prisma.raw intermediate constants outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-prisma-raw-intermediate-constants.ts": ` const tableName = '"Job"'; const fieldName = '"status"'; const query = Prisma.sql\`UPDATE \${Prisma.raw(tableName)} SET \${Prisma.raw(fieldName)} = \${status} WHERE id = \${id}\`; await db.$executeRaw(query); ` } }, { label: "Prisma.sql Job status write through dynamic Prisma.raw table outside JobExecutionStore", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-prisma-raw-dynamic-table.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const fieldName = '"status"'; const query = Prisma.sql\`UPDATE \${Prisma.raw(tableName)} SET \${Prisma.raw(fieldName)} = \${status} WHERE id = \${id}\`; await db.$executeRaw(query); ` } }, { label: "raw SQL AuditLog update outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-update.ts": 'await db.$executeRawUnsafe("UPDATE \\"AuditLog\\" SET \\"action\\" = $1 WHERE id = $2", action, id);' } }, { label: "raw SQL AuditLog inheritance update outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-inheritance-update.ts": 'await db.$executeRawUnsafe("UPDATE \\"AuditLog\\" * SET \\"action\\" = $1 WHERE id = $2", action, id);' } }, { label: "raw SQL AuditLog delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-delete.ts": 'await db.$executeRawUnsafe("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog identity-escaped keyword delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-identity-escaped-delete.ts": 'await db.$executeRawUnsafe("\\D\\E\\L\\E\\T\\E FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog hex-escaped delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-hex-delete.ts": 'await db.$executeRawUnsafe("DELETE FROM \\"\\x41uditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog unicode quoted identifier delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-unicode-quoted-identifier-delete.ts": `await db.$executeRawUnsafe('DELETE FROM U&"Audit\\\\004Cog" WHERE id = $1', id);` } }, { label: "raw SQL AuditLog custom UESCAPE unicode quoted identifier delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-unicode-quoted-custom-uescape-delete.ts": `await db.$executeRawUnsafe('DELETE FROM U&"Audit!004Cog" UESCAPE \\'!\\' WHERE id = $1', id);` } }, { label: "raw SQL AuditLog delete after line-comment-looking string literal", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-delete-after-string-line-comment.ts": `await db.$executeRawUnsafe("SELECT '-- not a comment'; DELETE FROM \\"AuditLog\\" WHERE id = $1", id);` } }, { label: "raw SQL AuditLog delete after block-comment-looking string literal", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-delete-after-string-block-comment.ts": `await db.$executeRawUnsafe("SELECT '/* not a comment */'; DELETE FROM \\"AuditLog\\" WHERE id = $1", id);` } }, { label: "raw SQL AuditLog upsert outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-upsert.ts": 'await db.$executeRawUnsafe("INSERT INTO \\"AuditLog\\" (\\"id\\", \\"action\\") VALUES ($1, $2) ON CONFLICT (\\"id\\") DO UPDATE SET \\"action\\" = EXCLUDED.\\"action\\"", id, action);' } }, { label: "raw SQL AuditLog block-comment upsert outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-block-comment-upsert.ts": 'await db.$executeRawUnsafe("INSERT/*x*/INTO \\"AuditLog\\" (\\"id\\", \\"action\\") VALUES ($1, $2) ON/*x*/CONFLICT (\\"id\\") DO/*x*/UPDATE SET \\"action\\" = EXCLUDED.\\"action\\"", id, action);' } }, { label: "raw SQL AuditLog merge outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-merge.ts": 'await db.$executeRawUnsafe("MERGE INTO \\"AuditLog\\" a USING \\"AuditLogPatch\\" p ON a.id = p.id WHEN MATCHED THEN UPDATE SET \\"action\\" = p.\\"action\\"");' } }, { label: "raw SQL AuditLog block-comment merge outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-block-comment-merge.ts": 'await db.$executeRawUnsafe("MERGE/*x*/INTO \\"AuditLog\\" a USING \\"AuditLogPatch\\" p ON a.id = p.id WHEN MATCHED THEN UPDATE SET \\"action\\" = p.\\"action\\"");' } }, { label: "raw SQL AuditLog block-comment delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-block-comment-delete.ts": 'await db.$executeRawUnsafe("DELETE/*x*/FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog block-comment update outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-block-comment-update.ts": 'await db.$executeRawUnsafe("UPDATE/*x*/\\"AuditLog\\" SET \\"action\\" = $1 WHERE id = $2", action, id);' } }, { label: "raw SQL AuditLog delete through dot whitespace executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-dot-whitespace-delete.ts": 'await db. $executeRawUnsafe("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through optional receiver executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-receiver-delete.ts": 'await db?.$executeRawUnsafe("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through bracket executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-delete.ts": 'await db["$executeRawUnsafe"]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through optional bracket executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-bracket-delete.ts": 'await db?.["$executeRawUnsafe"]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through computed executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-delete.ts": 'await db["$execute" + "RawUnsafe"]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through three-part computed executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-three-part-delete.ts": 'await db["$execute" + "Raw" + "Unsafe"]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through computed executor name alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-alias-delete.ts": ` const rawName = "$executeRawUnsafe"; await db[rawName]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through optional computed executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-computed-delete.ts": 'await db?.["$execute" + "RawUnsafe"]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through parenthesized executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-parenthesized-delete.ts": 'await (db.$executeRawUnsafe)("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through parenthesized bracket executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-parenthesized-bracket-delete.ts": 'await (db["$executeRawUnsafe"])("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through bracket call executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-call-delete.ts": 'await db["$executeRawUnsafe"].call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through optional bracket call executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-bracket-call-delete.ts": 'await db?.["$executeRawUnsafe"]?.call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through bracket call operation executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-call-operation-delete.ts": 'await db.$executeRawUnsafe["call"](db, \'DELETE FROM "AuditLog" WHERE id = $1\', id);' } }, { label: "raw SQL AuditLog delete through computed call operation executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-call-operation-delete.ts": 'await db.$executeRawUnsafe["ca" + "ll"](db, \'DELETE FROM "AuditLog" WHERE id = $1\', id);' } }, { label: "raw SQL AuditLog delete through bracket bind executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-bind-delete.ts": 'await db["$executeRawUnsafe"].bind(db)("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through bracket bind operation executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-bind-operation-delete.ts": 'await db.$executeRawUnsafe["bind"](db)(\'DELETE FROM "AuditLog" WHERE id = $1\', id);' } }, { label: "raw SQL AuditLog delete through computed bind operation executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-bind-operation-delete.ts": 'await db.$executeRawUnsafe["bi" + "nd"](db)(\'DELETE FROM "AuditLog" WHERE id = $1\', id);' } }, { label: "raw SQL AuditLog delete through apply executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-apply-delete.ts": 'await db.$executeRawUnsafe.apply(db, ["DELETE FROM \\"AuditLog\\" WHERE id = $1", id]);' } }, { label: "raw SQL Job status write through bracket apply operation executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-bracket-apply-operation-status.ts": 'await db.$executeRawUnsafe["apply"](db, [\'UPDATE "Job" SET "status" = $1 WHERE id = $2\', status, id]);' } }, { label: "raw SQL AuditLog delete through Function.prototype.call executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-delete.ts": 'Function.prototype.call.call(db.$executeRawUnsafe, db, \'DELETE FROM "AuditLog" WHERE id = $1\', id);' } }, { label: "raw SQL AuditLog delete through Function.prototype.call.apply executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-apply-delete.ts": 'Function.prototype.call.apply(db.$executeRawUnsafe, [db, \'DELETE FROM "AuditLog" WHERE id = $1\', id]);' } }, { label: "raw SQL AuditLog delete through Function.prototype.call alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-alias-delete.ts": ` const c = Function.prototype.call; c.call(db.$executeRawUnsafe, db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through Function.prototype.call alias apply executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-alias-apply-delete.ts": ` const c = Function.prototype.call; c.apply(db.$executeRawUnsafe, [db, 'DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL AuditLog delete through assigned Function.prototype.call alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-assigned-alias-delete.ts": ` let c; c = Function.prototype.call; c.call(db.$executeRawUnsafe, db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL Job status write through Reflect.apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-status.ts": 'Reflect.apply(db.$executeRawUnsafe, db, ["UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id]);' } }, { label: "raw SQL AuditLog delete through Reflect.apply.call executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-call-delete.ts": ` Reflect.apply.call( Reflect, db.$executeRawUnsafe, db, ["DELETE FROM \\"AuditLog\\" WHERE id = $1", id] ); ` } }, { label: "raw SQL Job status write through Reflect.apply.apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-apply-status.ts": ` Reflect.apply.apply(Reflect, [ db.$executeRawUnsafe, db, ["UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id] ]); ` } }, { label: "raw SQL AuditLog delete through Reflect.apply Function.prototype.call executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-function-prototype-call-delete.ts": 'Reflect.apply(Function.prototype.call, db.$executeRawUnsafe, [db, \'DELETE FROM "AuditLog" WHERE id = $1\', id]);' } }, { label: "raw SQL AuditLog delete through Reflect.apply alias Function.prototype.call executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-alias-function-prototype-call-delete.ts": ` const ra = Reflect.apply; ra(Function.prototype.call, db.$executeRawUnsafe, [db, 'DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL Job status write through Function.prototype.apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-function-prototype-apply-status.ts": 'Function.prototype.apply.call(db.$executeRawUnsafe, db, [\'UPDATE "Job" SET "status" = $1 WHERE id = $2\', status, id]);' } }, { label: "raw SQL Job status write through Function.prototype.apply.apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-function-prototype-apply-apply-status.ts": 'Function.prototype.apply.apply(db.$executeRawUnsafe, [db, [\'UPDATE "Job" SET "status" = $1 WHERE id = $2\', status, id]]);' } }, { label: "raw SQL Job status write through Function.prototype.apply alias executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-function-prototype-apply-alias-status.ts": ` const a = Function.prototype.apply; a.call(db.$executeRawUnsafe, db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]); ` } }, { label: "raw SQL Job status write through Function.prototype.apply alias apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-function-prototype-apply-alias-apply-status.ts": ` const a = Function.prototype.apply; a.apply(db.$executeRawUnsafe, [db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]]); ` } }, { label: "raw SQL Job status write through Reflect.apply Function.prototype.apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-function-prototype-apply-status.ts": 'Reflect.apply(Function.prototype.apply, db.$executeRawUnsafe, [db, [\'UPDATE "Job" SET "status" = $1 WHERE id = $2\', status, id]]);' } }, { label: "raw SQL Job status write through assigned Reflect.apply alias Function.prototype.apply executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-assigned-alias-function-prototype-apply-status.ts": ` let ra; ra = Reflect.apply; ra(Function.prototype.apply, db.$executeRawUnsafe, [db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]]); ` } }, { label: "raw SQL AuditLog delete through Reflect.apply alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-alias-direct-delete.ts": ` const ra = Reflect.apply; ra(db.$executeRawUnsafe, db, ['DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL AuditLog delete through propagated Reflect.apply alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-propagated-alias-direct-delete.ts": ` const r1 = Reflect.apply; const r2 = r1; r2(db.$executeRawUnsafe, db, ['DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL AuditLog delete through optional Reflect.apply alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-reflect-apply-alias-direct-delete.ts": ` const ra = Reflect.apply; ra?.(db.$executeRawUnsafe, db, ['DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL AuditLog delete through bound Reflect.apply alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bound-reflect-apply-alias-delete.ts": ` const ra = Reflect.apply.bind(Reflect); ra(db.$executeRawUnsafe, db, ["DELETE FROM \\"AuditLog\\" WHERE id = $1", id]); ` } }, { label: "raw SQL AuditLog delete through null-bound Reflect.apply alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-null-bound-reflect-apply-alias-delete.ts": ` const ra = Reflect.apply.bind(null); ra(db.$executeRawUnsafe, db, ["DELETE FROM \\"AuditLog\\" WHERE id = $1", id]); ` } }, { label: "raw SQL Job status write through optional bound Reflect.apply alias executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-optional-bound-reflect-apply-alias-status.ts": ` const ra = Reflect.apply.bind(Reflect); ra?.(db.$executeRawUnsafe, db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]); ` } }, { label: "raw SQL AuditLog delete through bound Function.prototype.call alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bound-function-prototype-call-alias-delete.ts": ` const c = Function.prototype.call.bind(Function.prototype.call); c(db.$executeRawUnsafe, db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through bracket bound Function.prototype.call alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-bound-function-prototype-call-alias-delete.ts": ` const c = Function.prototype["call"].bind(Function.prototype["call"]); c(db.$executeRawUnsafe, db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through optional bracket bound Function.prototype.call alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-bracket-bound-function-prototype-call-alias-delete.ts": ` const pc = Function.prototype["call"].bind(Function.prototype["call"]); pc?.(db.$executeRawUnsafe, db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through computed bound Function.prototype.call alias executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-bound-function-prototype-call-alias-delete.ts": ` const c = Function.prototype["ca" + "ll"].bind(Function.prototype["ca" + "ll"]); c(db.$executeRawUnsafe, db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL Job status write through bracket bound Function.prototype.apply alias executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-bracket-bound-function-prototype-apply-alias-status.ts": ` const a = Function.prototype["apply"].bind(Function.prototype["apply"]); a(db.$executeRawUnsafe, db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]); ` } }, { label: "raw SQL AuditLog delete through Function.prototype.call bound target immediate call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-bind-target-delete.ts": ` Function.prototype.call.bind(db.$executeRawUnsafe)( db, 'DELETE FROM "AuditLog" WHERE id = $1', id ); ` } }, { label: "raw SQL AuditLog delete through optional Function.prototype call bind target immediate call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-optional-call-bind-target-delete.ts": ` Function.prototype.call?.bind(db.$executeRawUnsafe)( db, 'DELETE FROM "AuditLog" WHERE id = $1', id ); ` } }, { label: "raw SQL AuditLog delete through bracket Function.prototype call bind target immediate call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-bracket-call-bind-target-delete.ts": ` Function.prototype["call"].bind(db.$executeRawUnsafe)( db, 'DELETE FROM "AuditLog" WHERE id = $1', id ); ` } }, { label: "raw SQL AuditLog delete through computed Function.prototype call bind target immediate call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-computed-call-bind-target-delete.ts": ` Function.prototype["ca" + "ll"].bind(db.$executeRawUnsafe)( db, 'DELETE FROM "AuditLog" WHERE id = $1', id ); ` } }, { label: "raw SQL Job status write through Function.prototype.apply bound target immediate call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-function-prototype-apply-bind-target-status.ts": ` Function.prototype.apply.bind(db.$executeRawUnsafe)( db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id] ); ` } }, { label: "raw SQL Job status write through computed Function.prototype apply bind target immediate call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-function-prototype-computed-apply-bind-target-status.ts": ` Function.prototype["ap" + "ply"].bind(db.$executeRawUnsafe)( db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id] ); ` } }, { label: "raw SQL AuditLog delete through Function.prototype.call bound target alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-bind-target-alias-delete.ts": ` const exec = Function.prototype.call.bind(db.$executeRawUnsafe); exec(db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through optional Function.prototype.call bound target alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-function-prototype-call-bind-target-alias-delete.ts": ` const exec = Function.prototype.call.bind(db.$executeRawUnsafe); exec?.(db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through assigned Function.prototype.call bound target alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-prototype-call-bind-target-assigned-alias-delete.ts": ` let exec; exec = Function.prototype.call.bind(db.$executeRawUnsafe); exec(db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL Job status write through Reflect.apply computed executor access", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-computed-status.ts": 'Reflect.apply((db["$execute" + "Raw" + "Unsafe"]), db, ["UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id]);' } }, { label: "raw SQL AuditLog delete through Reflect bracket apply executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-bracket-apply-delete.ts": ` Reflect["apply"](db.$executeRawUnsafe, db, [ 'DELETE FROM "AuditLog" WHERE id = $1', id ]); ` } }, { label: "raw SQL AuditLog delete through Reflect computed apply executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-computed-apply-delete.ts": ` Reflect["ap" + "ply"](db.$executeRawUnsafe, db, [ 'DELETE FROM "AuditLog" WHERE id = $1', id ]); ` } }, { label: "raw SQL Job status write through Reflect.apply executor name alias", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-alias-status.ts": ` const rawName = "$executeRawUnsafe"; Reflect.apply(db[rawName], db, ["UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id]); ` } }, { label: "raw SQL AuditLog delete through Reflect.apply executor name alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-alias-delete.ts": ` const rawName = "$executeRawUnsafe"; Reflect.apply(db[rawName], db, ["DELETE FROM \\"AuditLog\\" WHERE id = $1", id]); ` } }, { label: "guarded raw SQL update through Reflect.apply executor name alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-reflect-apply-alias-update.ts": ` const rawName = "$executeRawUnsafe"; Reflect.apply(db[rawName], db, ["UPDATE \\"GameVersion\\" SET \\"status\\" = $1 WHERE id = $2", status, id]); ` } }, { label: "dynamic raw SQL write through Reflect.apply executor name alias", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-alias-dynamic.ts": ` const rawName = "$executeRawUnsafe"; const tableName = process.env.TABLE; const query = "UPDATE " + tableName + " SET status = $1"; Reflect.apply(db[rawName], db, [query, status]); ` } }, { label: "raw SQL AuditLog delete through Reflect.apply plain executor alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-plain-alias-delete.ts": ` const execRaw = db.$executeRawUnsafe; Reflect.apply(execRaw, db, ['DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL Job status write through Reflect.apply bound executor alias", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-bound-alias-status.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); Reflect.apply(execRaw, db, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]); ` } }, { label: "guarded raw SQL update through Reflect.apply assigned executor alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-reflect-apply-assigned-alias-update.ts": ` let execRaw; execRaw = db.$executeRawUnsafe; Reflect.apply(execRaw, db, ['UPDATE "GameVersion" SET "status" = $1 WHERE id = $2', status, id]); ` } }, { label: "dynamic raw SQL write through Reflect.apply destructured executor alias", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/job-raw-reflect-apply-destructured-alias-dynamic.ts": ` const { $executeRawUnsafe: execRaw } = db; const tableName = process.env.TABLE; const query = "UPDATE " + tableName + " SET status = $1"; Reflect.apply(execRaw, db, [query, status]); ` } }, { label: "raw SQL AuditLog delete through Reflect.apply computed destructured executor alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-reflect-apply-computed-destructured-alias-delete.ts": ` const rawName = "$executeRawUnsafe"; const { [rawName]: execRaw } = db; Reflect.apply(execRaw, db, ['DELETE FROM "AuditLog" WHERE id = $1', id]); ` } }, { label: "raw SQL AuditLog delete through assigned executor name alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-assigned-computed-key-delete.ts": ` let rawName; rawName = "$executeRawUnsafe"; await db[rawName]('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through optional bracket bind executor access", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-bracket-bind-delete.ts": 'await db?.["$executeRawUnsafe"]?.bind(db)("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL AuditLog delete through bracket bound alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-bound-alias-delete.ts": ` const execRaw = db["$executeRawUnsafe"].bind(db); await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through plain alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-plain-alias-direct-delete.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through mixed ternary bound executor alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-mixed-ternary-bound-alias-delete.ts": ` const execRaw = ok ? db.$executeRawUnsafe.bind(db) : safe; await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through propagated plain alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-propagated-plain-alias-direct-delete.ts": ` const execRaw = db.$executeRawUnsafe; const runRaw = execRaw; await runRaw('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through parenthesized plain alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-parenthesized-plain-alias-direct-delete.ts": ` const execRaw = (db.$executeRawUnsafe); await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through destructured executor alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-destructured-alias-direct-delete.ts": ` const { $executeRawUnsafe } = db; await $executeRawUnsafe("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through renamed destructured executor alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-renamed-destructured-alias-direct-delete.ts": ` const { $executeRawUnsafe: execRaw } = db; await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through quoted destructured executor alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-quoted-destructured-alias-direct-delete.ts": ` const { "$executeRawUnsafe": execRaw } = db; await execRaw("UPDATE \\"AuditLog\\" SET action = $1 WHERE id = $2", action, id); ` } }, { label: "raw SQL AuditLog delete through computed destructured executor alias direct call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-computed-destructured-alias-direct-delete.ts": ` const { ["$executeRawUnsafe"]: execRaw } = db; await execRaw("UPDATE \\"AuditLog\\" SET action = $1 WHERE id = $2", action, id); ` } }, { label: "raw SQL AuditLog delete through post-declaration destructured executor alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-assigned-destructured-alias-delete.ts": ` let execRaw; ({ $executeRawUnsafe: execRaw } = db); await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through assigned computed destructured executor alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-assigned-computed-destructured-alias-delete.ts": ` let execRaw; ({ ["$executeRawUnsafe"]: execRaw } = db); await execRaw("UPDATE \\"AuditLog\\" SET action = $1 WHERE id = $2", action, id); ` } }, { label: "raw SQL Job status write through assigned executor alias direct call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-assigned-alias-direct-status.ts": ` let execRaw; execRaw = db.$executeRawUnsafe; await execRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL Job status write through assigned logical plain executor alias", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-assigned-logical-plain-alias-status.ts": ` let runRaw; runRaw = maybe || db.$executeRawUnsafe; await runRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL Job status write through post-declaration destructured executor alias", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-assigned-destructured-alias-status.ts": ` let $executeRawUnsafe; ({ $executeRawUnsafe } = db); await $executeRawUnsafe("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL Job status write through assigned bound executor alias direct call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-assigned-bound-alias-direct-status.ts": ` let execRaw; execRaw = db.$executeRawUnsafe.bind(db); await execRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL Job status write through propagated bound alias direct call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-propagated-bound-alias-direct-status.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); const runRaw = execRaw; await runRaw('UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id); ` } }, { label: "optional raw SQL Job status write through plain alias direct call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-plain-alias-optional-direct-status.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw?.("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL AuditLog delete through plain alias call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-plain-alias-call-delete.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw.call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL Job status write through plain alias call", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-plain-alias-call-status.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw.call(db, "UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "optional raw SQL AuditLog delete through plain alias call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-plain-alias-optional-call-delete.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw?.call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through plain alias bind", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-plain-alias-bind-delete.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw.bind(db)("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL Job status write through plain alias bind", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-plain-alias-bind-status.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw.bind(db)("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "optional raw SQL Job status write through plain alias bind", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-plain-alias-optional-bind-status.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw?.bind(db)("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL AuditLog delete through optional bracket plain alias call", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-bracket-plain-alias-call-delete.ts": ` const execRaw = db?.["$executeRawUnsafe"]; await execRaw.call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "no-semicolon bracket raw SQL AuditLog delete through bound alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bracket-bound-alias-no-semicolon-delete.ts": ` const execRaw = db["$executeRawUnsafe"].bind(db) await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id) ` } }, { label: "no-semicolon optional bracket raw SQL AuditLog delete through bound alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-bracket-bound-alias-no-semicolon-delete.ts": ` const execRaw = db?.["$executeRawUnsafe"]?.bind(db) await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id) ` } }, { label: "raw SQL AuditLog delete through object property wrapped executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-object-property-wrapper-delete.ts": ` const ops = { execRaw: db.$executeRawUnsafe }; await ops.execRaw.call(db, 'DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through object wrapper destructured executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-object-wrapper-destructured-delete.ts": ` const ops = { execRaw: db.$executeRawUnsafe }; const { execRaw } = ops; await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through object wrapper property alias", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-object-wrapper-property-alias-delete.ts": ` const ops = { execRaw: db.$executeRawUnsafe }; const runRaw = ops.execRaw; await runRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through object shorthand wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-object-shorthand-wrapper-delete.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); const ops = { execRaw }; await ops.execRaw('DELETE FROM "AuditLog" WHERE id=1'); ` } }, { label: "raw SQL AuditLog delete through Object.assign shorthand wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-object-assign-shorthand-wrapper-delete.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); const box = Object.assign({}, { execRaw }); await box.execRaw('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through array wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-array-wrapper-delete.ts": ` const ops = [db.$executeRawUnsafe]; await ops[0]("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through array wrapper skipped destructured executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-array-wrapper-skipped-destructured-delete.ts": ` const ops = [1, db.$executeRawUnsafe.bind(db)]; const [, execRaw] = ops; await execRaw('DELETE FROM "AuditLog" WHERE id=1'); ` } }, { label: "raw SQL AuditLog delete through array wrapper assigned skipped destructured executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-array-wrapper-assigned-skipped-destructured-delete.ts": ` const ops = [1, db.$executeRawUnsafe.bind(db)]; let execRaw; ([, execRaw] = ops); await execRaw('DELETE FROM "AuditLog" WHERE id=1'); ` } }, { label: "raw SQL AuditLog delete through function return wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-return-wrapper-delete.ts": ` function raw() { return db.$executeRawUnsafe; } await raw()("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through function returned object wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-return-object-wrapper-delete.ts": ` async function f(db) { function getBox() { return { raw: db.$executeRawUnsafe.bind(db) }; } const { raw } = getBox(); await raw('DELETE FROM "AuditLog" WHERE id=1'); } ` } }, { label: "raw SQL AuditLog delete through parenthesized function returned object wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-parenthesized-function-return-object-wrapper-delete.ts": ` function rawBox() { return ({ execRaw: db.$executeRawUnsafe }); } rawBox().execRaw('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through function returned array wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-return-array-wrapper-delete.ts": ` function getBox() { return [db.$executeRawUnsafe.bind(db)]; } await getBox()[0]('DELETE FROM "AuditLog" WHERE id=1'); ` } }, { label: "raw SQL guarded state truncate through parenthesized function returned array wrapper executor", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/review-raw-parenthesized-function-return-array-wrapper-truncate.ts": ` function rawArray() { return ([db.$executeRawUnsafe]); } rawArray()[0]('TRUNCATE TABLE "ReviewRecord"'); ` } }, { label: "raw SQL Job status write through parenthesized arrow returned object wrapper executor", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-parenthesized-arrow-return-object-wrapper-status.ts": ` const rawArrowBox = () => ({ execRaw: db.$executeRawUnsafe }); rawArrowBox().execRaw('UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id); ` } }, { label: "raw SQL AuditLog delete through typed arrow return wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-typed-arrow-return-wrapper-delete.ts": ` const raw = (): typeof db.$executeRawUnsafe => db.$executeRawUnsafe; raw()('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL Job status write through typed arrow returned object wrapper executor", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-raw-typed-arrow-return-object-wrapper-status.ts": ` const box = (): { execRaw: typeof db.$executeRawUnsafe } => ({ execRaw: db.$executeRawUnsafe }); box().execRaw('UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id); ` } }, { label: "raw SQL AuditLog delete through nested object wrapper destructured executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-nested-object-wrapper-destructured-delete.ts": ` const raw = { inner: { exec: db.$executeRawUnsafe } }; const { inner: { exec } } = raw; await exec('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through arrow return wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-arrow-return-wrapper-delete.ts": ` const raw = () => db.$executeRawUnsafe; await raw()('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through block arrow return wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-block-arrow-return-wrapper-delete.ts": ` const getRaw = () => { return db.$executeRawUnsafe.bind(db); }; await getRaw()('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through function expression return wrapper executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-function-expression-return-wrapper-delete.ts": ` const raw = function () { return db.$executeRawUnsafe; }; await raw()('DELETE FROM "AuditLog" WHERE id = $1', id); ` } }, { label: "raw SQL AuditLog delete through call outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-call-delete.ts": 'await db.$executeRawUnsafe.call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "optional raw SQL AuditLog delete through call outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-call-delete.ts": 'await db.$executeRawUnsafe?.call(db, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "optional raw SQL AuditLog delete outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-optional-delete.ts": 'await db.$executeRawUnsafe?.("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "bound raw SQL AuditLog delete alias outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-bound-alias-delete.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog multi-table truncate outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-raw-multi-truncate.ts": 'await db.$executeRawUnsafe("TRUNCATE TABLE \\"Other\\", \\"AuditLog\\"");' } }, { label: "raw SQL AuditLog IF EXISTS trigger disable outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-disable-trigger-if-exists.ts": 'await db.$executeRawUnsafe("ALTER TABLE IF EXISTS \\"AuditLog\\" DISABLE TRIGGER ALL");' } }, { label: "raw SQL AuditLog trigger disable outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-disable-trigger.ts": 'await db.$executeRawUnsafe("ALTER TABLE \\"AuditLog\\" DISABLE TRIGGER \\"AuditLog_reject_delete\\"");' } }, { label: "raw SQL AuditLog drop trigger outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-trigger.ts": 'await db.$executeRawUnsafe("DROP TRIGGER audit_append_only ON \\"AuditLog\\"");' } }, { label: "raw SQL AuditLog quoted drop trigger outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-trigger-quoted.ts": 'await db.$executeRawUnsafe("DROP TRIGGER \\"audit-append-only\\" ON \\"AuditLog\\";");' } }, { label: "raw SQL AuditLog multi-table drop outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-table-list.ts": 'await db.$executeRawUnsafe("DROP TABLE \\"Other\\", \\"AuditLog\\"");' } }, { label: "raw SQL AuditLog alter table drop outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-table-drop.ts": 'await db.$executeRawUnsafe("ALTER TABLE \\"AuditLog\\" DROP CONSTRAINT foo");' } }, { label: "raw SQL AuditLog drop append-only function outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-function.ts": 'await db.$executeRawUnsafe("DROP FUNCTION app_reject_audit_log_mutation() CASCADE");' } }, { label: "raw SQL AuditLog unicode-codepoint drop append-only function outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-function-unicode-codepoint.ts": 'await db.$executeRawUnsafe("DROP FUNCTION \\u{61}pp_reject_audit_log_mutation() CASCADE");' } }, { label: "raw SQL AuditLog replace append-only function outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-replace-function.ts": 'await db.$executeRawUnsafe("CREATE OR REPLACE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RETURN OLD; END; $$ LANGUAGE plpgsql");' } }, { label: "raw SQL AuditLog alter append-only function outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-function.ts": 'await db.$executeRawUnsafe("ALTER FUNCTION app_reject_audit_log_mutation() OWNER TO app");' } }, { label: "raw SQL AuditLog alter IF EXISTS append-only function outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-function-if-exists.ts": 'await db.$executeRawUnsafe("ALTER FUNCTION IF EXISTS app_reject_audit_log_mutation() OWNER TO postgres");' } }, { label: "raw SQL AuditLog multi-function drop append-only function outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-function-list.ts": 'await db.$executeRawUnsafe("DROP FUNCTION IF EXISTS harmless(), app_reject_audit_log_mutation() CASCADE");' } }, { label: "raw SQL AuditLog drop append-only routine outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-routine.ts": 'await db.$executeRawUnsafe("DROP ROUTINE IF EXISTS app_reject_audit_log_mutation() CASCADE");' } }, { label: "raw SQL AuditLog alter append-only routine outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-routine.ts": 'await db.$executeRawUnsafe("ALTER ROUTINE app_reject_audit_log_mutation() OWNER TO app");' } }, { label: "raw SQL AuditLog alter IF EXISTS append-only routine outside trigger assertion", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-routine-if-exists.ts": 'await db.$executeRawUnsafe("ALTER ROUTINE IF EXISTS app_reject_audit_log_mutation() OWNER TO postgres");' } }, { label: "raw SQL dynamic drop function outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-dynamic-function.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "DROP FUNCTION " + fn + " CASCADE"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL dynamic alter function outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-dynamic-function.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "ALTER FUNCTION " + fn + " OWNER TO app"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL dynamic drop routine outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-dynamic-routine.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "DROP ROUTINE " + fn + " CASCADE"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL dynamic alter routine outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-dynamic-routine.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "ALTER ROUTINE " + fn + " OWNER TO app"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL dynamic drop function list outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-dynamic-function-list.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "DROP FUNCTION IF EXISTS harmless(), " + fn + " CASCADE"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL dynamic create function outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-create-dynamic-function.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "CREATE FUNCTION " + fn + " RETURNS trigger AS $$ BEGIN RETURN OLD; END; $$ LANGUAGE plpgsql"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL dynamic create or replace function outside trigger assertion", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-replace-dynamic-function.ts": ` const fn = process.env.AUDIT_FN ?? 'app_reject_audit_log_mutation()'; const query = "CREATE OR REPLACE FUNCTION " + fn + " RETURNS trigger AS $$ BEGIN RETURN OLD; END; $$ LANGUAGE plpgsql"; await db.$executeRawUnsafe(query); ` } }, { label: "migration replace AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010102_replace_audit_guard/migration.sql": ` CREATE OR REPLACE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RAISE EXCEPTION 'AuditLog is append-only' USING ERRCODE = '42501'; END; $$ LANGUAGE plpgsql; ` } }, { label: "migration block-comment tampered AuditLog append-only function with RETURN NEW", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010108_block_comment_tampered_audit_guard_return/migration.sql": ` CREATE/*x*/FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RETURN NEW; RAISE EXCEPTION 'AuditLog is append-only' USING ERRCODE = '42501'; END; $$ LANGUAGE plpgsql; ` } }, { label: "migration tampered AuditLog append-only function with RETURN NEW", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010105_tampered_audit_guard_return/migration.sql": ` CREATE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RETURN NEW; RAISE EXCEPTION 'AuditLog is append-only' USING ERRCODE = '42501'; END; $$ LANGUAGE plpgsql; ` } }, { label: "migration tampered AuditLog append-only function with IF branch", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010106_tampered_audit_guard_if/migration.sql": ` CREATE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN IF false THEN RAISE EXCEPTION 'AuditLog is append-only' USING ERRCODE = '42501'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; ` } }, { label: "migration mixed canonical and bad AuditLog append-only function definitions", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010107_mixed_audit_guard/migration.sql": ` CREATE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RAISE EXCEPTION 'AuditLog is append-only' USING ERRCODE = '42501'; END; $$ LANGUAGE plpgsql; CREATE FUNCTION app_reject_audit_log_mutation() RETURNS trigger AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql; ` } }, { label: "migration drop AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010103_drop_audit_guard/migration.sql": "DROP FUNCTION app_reject_audit_log_mutation() CASCADE;" } }, { label: "migration drop AuditLog append-only function in list", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010120_drop_audit_guard_function_list/migration.sql": "DROP FUNCTION IF EXISTS harmless(), app_reject_audit_log_mutation() CASCADE;" } }, { label: "migration drop AuditLog append-only routine", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010121_drop_audit_guard_routine/migration.sql": "DROP ROUTINE IF EXISTS app_reject_audit_log_mutation() CASCADE;" } }, { label: "migration block-comment drop AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010109_block_comment_drop_audit_guard/migration.sql": "DROP/*x*/FUNCTION app_reject_audit_log_mutation() CASCADE;" } }, { label: "migration nested block-comment drop AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010116_nested_block_comment_drop_audit_guard/migration.sql": "DROP/* outer /* inner */ still outer */FUNCTION app_reject_audit_log_mutation() CASCADE;" } }, { label: "migration alter AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010104_alter_audit_guard/migration.sql": "ALTER FUNCTION app_reject_audit_log_mutation() OWNER TO app;" } }, { label: "migration alter IF EXISTS AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010124_alter_if_exists_audit_guard/migration.sql": "ALTER FUNCTION IF EXISTS app_reject_audit_log_mutation() OWNER TO postgres;" } }, { label: "migration alter AuditLog append-only routine", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010122_alter_audit_guard_routine/migration.sql": "ALTER ROUTINE app_reject_audit_log_mutation() OWNER TO app;" } }, { label: "migration alter IF EXISTS AuditLog append-only routine", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010125_alter_if_exists_audit_guard_routine/migration.sql": "ALTER ROUTINE IF EXISTS app_reject_audit_log_mutation() OWNER TO postgres;" } }, { label: "migration block-comment alter AuditLog append-only function", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010110_block_comment_alter_audit_guard/migration.sql": "ALTER/*x*/FUNCTION app_reject_audit_log_mutation() OWNER TO app;" } }, { label: "migration SQL AuditLog block-comment delete", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010111_block_comment_audit_delete/migration.sql": 'DELETE/*x*/FROM "AuditLog" WHERE id = \'x\';' } }, { label: "migration SQL AuditLog unicode quoted identifier delete", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010119_unicode_quoted_audit_delete/migration.sql": 'DELETE FROM U&"Audit\\004Cog" WHERE id = \'x\';' } }, { label: "migration SQL AuditLog custom UESCAPE unicode quoted identifier delete", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010123_unicode_quoted_custom_uescape_audit_delete/migration.sql": 'DELETE FROM U&"Audit!004Cog" UESCAPE \'!\' WHERE id = \'x\';' } }, { label: "migration SQL AuditLog inheritance update", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010118_audit_inheritance_update/migration.sql": 'UPDATE "AuditLog" * SET "action" = \'changed\' WHERE id = \'x\';' } }, { label: "migration SQL AuditLog nested block-comment delete", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010117_nested_block_comment_audit_delete/migration.sql": 'DELETE/* outer /* inner */ still outer */FROM "AuditLog" WHERE id = \'x\';' } }, { label: "migration SQL AuditLog delete after line-comment-looking string literal", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010115_audit_delete_after_string_line_comment/migration.sql": `SELECT '-- not a comment'; DELETE FROM "AuditLog" WHERE id = 'x';` } }, { label: "migration SQL AuditLog upsert", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010112_audit_upsert/migration.sql": 'INSERT INTO "AuditLog" ("id", "action") VALUES (\'x\', \'created\') ON CONFLICT ("id") DO UPDATE SET "action" = EXCLUDED."action";' } }, { label: "migration SQL AuditLog merge", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010113_audit_merge/migration.sql": 'MERGE INTO "AuditLog" a USING "AuditLogPatch" p ON a.id = p.id WHEN MATCHED THEN UPDATE SET "action" = p."action";' } }, { label: "migration SQL AuditLog block-comment merge", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/prisma/migrations/20260602010114_block_comment_audit_merge/migration.sql": 'MERGE/*x*/INTO "AuditLog" a USING "AuditLogPatch" p ON a.id = p.id WHEN MATCHED THEN UPDATE SET "action" = p."action";' } }, { label: "migration SQL Job block-comment merge outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/prisma/migrations/20260602010112_block_comment_job_merge/migration.sql": 'MERGE/*x*/INTO "Job" j USING "JobPatch" p ON j.id = p.id WHEN MATCHED THEN UPDATE SET "status" = \'failed\';' } }, { label: "raw SQL AuditLog delete through direct generic executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-direct-generic-raw-delete.ts": 'await db.$executeRawUnsafe("DELETE FROM \\"AuditLog\\" WHERE id = $1", id);' } }, { label: "raw SQL Job update through direct generic executor", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-direct-generic-raw-update.ts": 'await db.$executeRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "raw SQL Job update through bound alias generic executor", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bound-alias-generic-raw-update.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); await execRaw("UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id); ` } }, { label: "raw SQL AuditLog delete through plain alias generic executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-plain-alias-generic-raw-delete.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through optional plain alias generic executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-optional-plain-alias-generic-raw-delete.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw?.("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog delete through wrapped generic executor", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-wrapped-generic-raw-delete.ts": ` const box = { execRaw: db.$executeRawUnsafe.bind(db) }; await box.execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1", id); ` } }, { label: "raw SQL AuditLog trigger disable through intermediate table constant", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-disable-trigger-intermediate.ts": ` const tableName = '"AuditLog"'; const query = "ALTER TABLE " + tableName + " DISABLE TRIGGER ALL"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL AuditLog IF EXISTS trigger disable through dynamic table variable", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-disable-trigger-if-exists-dynamic-table.ts": ` const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = "ALTER TABLE IF EXISTS " + tableName + " DISABLE TRIGGER ALL"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL multi-table truncate through dynamic table variable", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-multi-truncate-dynamic-table.ts": ` const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = "TRUNCATE TABLE \\"Other\\", " + tableName; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL AuditLog trigger disable through dynamic table variable", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-disable-trigger-dynamic-table.ts": ` const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = "ALTER TABLE " + tableName + " DISABLE TRIGGER ALL"; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL drop trigger through dynamic trigger and table variables", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-trigger-dynamic.ts": ` const triggerName = process.env.TRIGGER_NAME ?? 'audit_append_only'; const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = "DROP TRIGGER " + triggerName + " ON " + tableName; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL drop table through dynamic table variable", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-drop-table-dynamic.ts": ` const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = "DROP TABLE " + tableName; await db.$executeRawUnsafe(query); ` } }, { label: "raw SQL alter table drop through dynamic table variable", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-alter-drop-dynamic.ts": ` const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = "ALTER TABLE " + tableName + " DROP CONSTRAINT foo"; await db.$executeRawUnsafe(query); ` } }, { label: "Prisma.sql AuditLog delete through Prisma.raw intermediate table constant", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-prisma-raw-intermediate.ts": ` const tableName = '"AuditLog"'; const query = Prisma.sql\`DELETE FROM \${Prisma.raw(tableName)} WHERE id = \${id}\`; await db.$executeRaw(query); ` } }, { label: "Prisma.sql AuditLog delete through unicode-escaped Prisma.raw intermediate table constant", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-prisma-raw-unicode-intermediate.ts": ` const tableName = '"\\u0041uditLog"'; const query = Prisma.sql\`DELETE FROM \${Prisma.raw(tableName)} WHERE id = \${id}\`; await db.$executeRaw(query); ` } }, { label: "Prisma.sql AuditLog delete through dynamic Prisma.raw table", reason: "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-prisma-raw-dynamic-table.ts": ` const tableName = process.env.AUDIT_TABLE ?? '"AuditLog"'; const query = Prisma.sql\`DELETE FROM \${Prisma.raw(tableName)} WHERE id = \${id}\`; await db.$executeRaw(query); ` } }, { label: "direct Job status write inside jobs module outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/jobs/bad.ts": 'await db.job.update({ where: { id }, data: { status: "failed", attempts: { increment: 1 }, nextRetryAt: null } });' } }, { label: "direct Job status write inside jobs index outside JobExecutionStore class", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/jobs/index.ts": ` export class JobExecutionStore { async ok(db) { await db.job.update({ where: { id: "ok" }, data: { status: "running" } }); } } export class JobApiService { async bad(db) { await db.job.update({ where: { id: "bad" }, data: { status: "failed" } }); } } ` } }, { label: "computed Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-computed-bracket-model.ts": 'await db["j" + "o" + "b"].update({ where: { id }, data: { status: "failed" } });' } }, { label: "parenthesized computed Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-parenthesized-computed-bracket-model.ts": 'await db[("j" + "o" + "b")].update({ where: { id }, data: { status: "failed" } });' } }, { label: "computed Job update method write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-computed-bracket-method.ts": 'await db.job[("up" + "date")]({ where: { id }, data: { status: "failed" } });' } }, { label: "Function.prototype.call Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-function-prototype-call-status.ts": "Function.prototype.call.call(db.job.update, db.job, { where: { id }, data: { status } });" } }, { label: "Function.prototype.call propagated alias Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-function-prototype-call-propagated-alias-status.ts": ` const c1 = Function.prototype.call; const c2 = c1; c2.call(db.job.update, db.job, { where: { id }, data: { status: "failed" } }); ` } }, { label: "bracket call operation audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-bracket-call-operation-delete.ts": 'await db.auditLog.delete["call"](db.auditLog, { where: { id } });' } }, { label: "bracket call operation guarded Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-bracket-call-operation-status.ts": 'await db["gameVersion"].update["call"](db.gameVersion, { where: { id }, data: { status: "x" } });' } }, { label: "bracket apply operation Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bracket-apply-operation-status.ts": 'await db.job.update["apply"](db.job, [{ where: { id }, data: { status: "failed" } }]);' } }, { label: "computed apply operation Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-computed-apply-operation-status.ts": 'await db.job.update["app" + "ly"](db.job, [{ where: { id }, data: { status: "failed" } }]);' } }, { label: "bracket bind operation Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bracket-bind-operation-status.ts": 'await db.job.update["bind"](db.job)({ where: { id }, data: { status: "failed" } });' } }, { label: "delegate propagated alias Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-delegate-propagated-alias-status.ts": ` const jobs = db.job; const queueJobs = jobs; await queueJobs.update({ where: { id }, data: { status: "failed" } }); ` } }, { label: "delegate mixed ternary alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-delegate-mixed-ternary-alias-delete.ts": ` const jobs = ok ? db.job : safeJobs; await jobs.delete({ where: { id } }); ` } }, { label: "delegate assigned logical alias audit delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-delegate-assigned-logical-alias-delete.ts": ` let logs; logs = maybe && db.auditLog; await logs.delete({ where: { id } }); ` } }, { label: "delegate mixed ternary alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-delegate-mixed-ternary-alias-status.ts": ` const versions = ok ? db.gameVersion : safeVersions; await versions.update({ where: { id }, data: { status: "active" } }); ` } }, { label: "delegate object wrapper destructured Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-delegate-object-wrapper-destructured-status.ts": ` const ops = { jobs: db.job }; const { jobs } = ops; await jobs.update({ where: { id }, data: { status: "failed" } }); ` } }, { label: "delegate nested object wrapper destructured guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-delegate-nested-object-wrapper-destructured-status.ts": ` const box = { inner: { version: db.gameVersion } }; const { inner: { version } } = box; await version.update({ where: { id }, data: { status: "active" } }); ` } }, { label: "delegate array wrapper Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-delegate-array-wrapper-status.ts": ` const ops = [db.job]; await ops[0].update({ where: { id }, data: { status: "failed" } }); ` } }, { label: "delegate function return wrapper audit delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-delegate-function-return-wrapper-delete.ts": ` function audit() { return db.auditLog; } await audit().delete({ where: { id } }); ` } }, { label: "delegate function returned object wrapper audit delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-delegate-function-return-object-wrapper-delete.ts": ` async function f(db) { function getBox() { return { logs: db.auditLog }; } const { logs } = getBox(); await logs.delete({ where: { id: "1" } }); } ` } }, { label: "delegate arrow return wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-delegate-arrow-return-wrapper-status.ts": ` const getVersion = () => db.gameVersion; await getVersion().update({ where: { id }, data: { status: "active" } }); ` } }, { label: "delegate block arrow return wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-delegate-block-arrow-return-wrapper-status.ts": ` const getVersion = () => { return db.gameVersion; }; await getVersion().update({ where: { id }, data: { status: "active" } }); ` } }, { label: "delegate function returned object wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-delegate-function-return-object-wrapper-status.ts": ` async function f(db) { function getBox() { return { versions: db.gameVersion }; } const { versions } = getBox(); await versions.update({ where: { id: "1" }, data: { status: "x" } }); } ` } }, { label: "delegate parenthesized function returned object wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-delegate-parenthesized-function-return-object-wrapper-status.ts": ` function delegateBox() { return ({ versions: db.gameVersion }); } delegateBox().versions.update({ where: { id }, data: { status: 'active' } }); ` } }, { label: "delegate function expression return wrapper audit delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-delegate-function-expression-return-wrapper-delete.ts": ` const audit = function () { return db.auditLog; }; await audit().delete({ where: { id } }); ` } }, { label: "method object wrapper Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-object-wrapper-status.ts": ` const ops = { updateJob: db.job.update }; await ops.updateJob.call(db.job, { where: { id }, data: { status: "failed" } }); ` } }, { label: "method bound alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-bound-alias-delete.ts": ` const deleteJob = db.job.delete.bind(db.job); await deleteJob({ where: { id } }); ` } }, { label: "method plain alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-plain-alias-delete.ts": ` const del = db.job.delete; await del({ where: { id } }); ` } }, { label: "method assigned plain alias Job deleteMany outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-assigned-plain-alias-delete-many.ts": ` let delMany; delMany = db.job.deleteMany; await delMany({ where: { status: "running" } }); ` } }, { label: "method mixed ternary plain alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-mixed-ternary-plain-alias-delete.ts": ` const del = ok ? db.job.delete : safe; await del.call(db.job, { where: { id } }); ` } }, { label: "method mixed ternary bound alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-mixed-ternary-bound-alias-delete.ts": ` const del = ok ? db.job.delete.bind(db.job) : safe; await del({ where: { id } }); ` } }, { label: "method assigned bound alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-assigned-bound-alias-delete.ts": ` let del; del = db.job.delete.bind(db.job); await del({ where: { id } }); ` } }, { label: "method mixed assigned ternary bound alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-mixed-assigned-ternary-bound-alias-delete.ts": ` let del; del = ok ? db.job.delete.bind(db.job) : safe; await del({ where: { id } }); ` } }, { label: "method destructured alias Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-destructured-alias-delete.ts": ` const { delete: deleteJob } = db.job; await deleteJob.call(db.job, { where: { id } }); ` } }, { label: "method Object.assign wrapper Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-object-assign-wrapper-delete.ts": ` const deleteJob = db.job.delete.bind(db.job); const box = Object.assign({}, { deleteJob }); await box.deleteJob({ where: { id } }); ` } }, { label: "method function return wrapper Job deleteMany outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-method-function-return-wrapper-delete-many.ts": ` const getDeleteMany = () => { return db.job.deleteMany.bind(db.job); }; await getDeleteMany()({ where: { status: "running" } }); ` } }, { label: "method direct bound alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-direct-bound-alias-delete.ts": ` const del = db.auditLog.delete.bind(db.auditLog); await del({ where: { id } }); ` } }, { label: "method plain alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-plain-alias-delete.ts": ` const deleteAudit = db.auditLog.delete; await deleteAudit({ where: { id } }); ` } }, { label: "method assigned mixed ternary plain alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-assigned-mixed-ternary-plain-alias-delete.ts": ` let del; del = ok ? db.auditLog.delete : safe; await del({ where: { id } }); ` } }, { label: "method assigned bound alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-assigned-bound-alias-delete.ts": ` let del; del = db.auditLog.delete.bind(db.auditLog); await del({ where: { id } }); ` } }, { label: "method mixed ternary bound alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-mixed-ternary-bound-alias-delete.ts": ` const del = ok ? db.auditLog.delete.bind(db.auditLog) : safe; await del({ where: { id } }); ` } }, { label: "method destructured bound alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-destructured-bound-alias-delete.ts": ` const { delete: del0 } = db.auditLog; const del = del0.bind(db.auditLog); await del({ where: { id } }); ` } }, { label: "method direct bound alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-direct-bound-alias-status.ts": ` const updateVersion = db.gameVersion.update.bind(db.gameVersion); await updateVersion({ where: { id }, data: { status: "active" } }); ` } }, { label: "method assigned plain alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-assigned-plain-alias-status.ts": ` let updateVersion; updateVersion = db.gameVersion.update; await updateVersion({ where: { id }, data: { status: "active" } }); ` } }, { label: "method plain alias call guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-plain-alias-call-status.ts": ` const updateVersion = db.gameVersion.update; await updateVersion.call(db.gameVersion, { where: { id }, data: { status: "active" } }); ` } }, { label: "method logical plain alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-logical-plain-alias-call-status.ts": ` const update = maybe || db.gameVersion.update; await update.call(db.gameVersion, { where: { id }, data: { status: "active" } }); ` } }, { label: "method assigned bound alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-assigned-bound-alias-status.ts": ` let update; update = db.gameVersion.update.bind(db.gameVersion); await update({ where: { id }, data: { status: "active" } }); ` } }, { label: "method mixed ternary bound alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-mixed-ternary-bound-alias-status.ts": ` const update = ok ? db.gameVersion.update.bind(db.gameVersion) : safe; await update({ where: { id }, data: { status: "active" } }); ` } }, { label: "method ternary bound alias guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-ternary-bound-alias-status.ts": ` const update = ok ? db.gameVersion.update.bind(db.gameVersion) : db.gameVersion.update.bind(db.gameVersion); await update({ where: { id }, data: { status: 'active' } }); ` } }, { label: "method logical OR bound alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-logical-or-bound-alias-delete.ts": ` const del = maybe || db.auditLog.delete.bind(db.auditLog); await del({ where: { id } }); ` } }, { label: "method logical AND bound alias audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-method-logical-and-bound-alias-delete.ts": ` const del = ok && db.auditLog.delete.bind(db.auditLog); await del({ where: { id } }); ` } }, { label: "method Object.assign wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-object-assign-wrapper-status.ts": ` const update = db.gameVersion.update.bind(db.gameVersion); const box = Object.assign({ safe: 1 }, { update }); await box.update({ where: { id }, data: { status: "active" } }); ` } }, { label: "method block arrow return wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-block-arrow-return-wrapper-status.ts": ` const getUpdate = () => { return db.gameVersion.update; }; await getUpdate().call(db.gameVersion, { where: { id }, data: { status: "active" } }); ` } }, { label: "method object wrapper destructured guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-object-wrapper-destructured-status.ts": ` const ops = { update: db.gameVersion.update }; const { update } = ops; await update({ where: { id }, data: { status: "active" } }); ` } }, { label: "method object wrapper assigned destructured guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-object-wrapper-assigned-destructured-status.ts": ` const ops = { update: db.gameVersion.update }; let update; ({ update } = ops); await update.call(db.gameVersion, { where: { id }, data: { status: "active" } }); ` } }, { label: "method object wrapper property alias guarded call outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-object-wrapper-property-alias-call-status.ts": ` const ops = { update: db.gameVersion.update }; const fn = ops.update; await fn.call(db.gameVersion, { where: { id }, data: { status: "active" } }); ` } }, { label: "method object wrapper property alias guarded apply outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-object-wrapper-property-alias-apply-status.ts": ` const ops = { update: db.gameVersion.update }; const fn = ops.update; await fn.apply(db.gameVersion, [{ where: { id }, data: { status: "active" } }]); ` } }, { label: "method object wrapper property alias guarded bind outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-object-wrapper-property-alias-bind-status.ts": ` const ops = { update: db.gameVersion.update }; const fn = ops.update; await fn.bind(db.gameVersion)({ where: { id }, data: { status: "active" } }); ` } }, { label: "method function returned object wrapper property alias guarded call outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-function-return-object-wrapper-property-alias-call-status.ts": ` function getBox() { return { update: db.gameVersion.update }; } const update = getBox().update; await update.call(db.gameVersion, { where: { id }, data: { status: "x" } }); ` } }, { label: "method parenthesized function returned object wrapper guarded write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-method-parenthesized-function-return-object-wrapper-status.ts": ` function methodBox() { return ({ update: db.gameVersion.update }); } methodBox().update({ where: { id }, data: { status: 'active' } }); ` } }, { label: "Function.prototype.call.apply Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-function-prototype-call-apply-status.ts": "Function.prototype.call.apply(db.job.update, [db.job, { where: { id }, data: { status: 'failed' } }]);" } }, { label: "Function.prototype.call.apply audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-function-prototype-call-apply-delete.ts": "Function.prototype.call.apply(db.auditLog.delete, [db.auditLog, { where: { id } }]);" } }, { label: "Function.prototype.call bound target audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-function-prototype-call-bind-target-delete.ts": ` Function.prototype.call.bind(db.auditLog.delete)( db.auditLog, { where: { id } } ); ` } }, { label: "Function.prototype.apply bound target Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-function-prototype-apply-bind-target-status.ts": ` Function.prototype.apply.bind(db.job.update)( db.job, [{ where: { id }, data: { status: "failed" } }] ); ` } }, { label: "Function.prototype computed apply bound target Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-function-prototype-computed-apply-bind-target-status.ts": ` Function.prototype["ap" + "ply"].bind(db.job.update)( db.job, [{ where: { id }, data: { status: "failed" } }] ); ` } }, { label: "bracket bound Function.prototype.call alias Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-bracket-bound-function-prototype-call-alias-status.ts": ` const pc = Function.prototype["call"].bind(Function.prototype["call"]); pc(db.job.update, db.job, { where: { id }, data: { status: "failed" } }); ` } }, { label: "optional bracket bound Function.prototype.call alias Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-optional-bracket-bound-function-prototype-call-alias-status.ts": ` const pc = Function.prototype["call"].bind(Function.prototype["call"]); pc?.(db.job.update, db.job, { where: { id }, data: { status: "failed" } }); ` } }, { label: "Reflect bracket apply guarded Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-reflect-bracket-apply-prisma-update.ts": ` Reflect["apply"](db.gameVersion.update, db.gameVersion, [ { where: { id }, data: { status: "active" } } ]); ` } }, { label: "null-bound Reflect.apply Job delete outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-null-bound-reflect-apply-delete.ts": ` const apply = Reflect.apply.bind(null); apply(db.job.delete, db.job, [{ where: { id } }]); ` } }, { label: "null-bound Reflect.apply audit delegate delete outside audit module", reason: "AUDIT_UPDATE_DELETE_FORBIDDEN", files: { "apps/api/src/modules/projects/audit-null-bound-reflect-apply-delete.ts": ` const apply = Reflect.apply.bind(null); apply(db.auditLog.delete, db.auditLog, [{ where: { id } }]); ` } }, { label: "null-bound Reflect.apply guarded Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-null-bound-reflect-apply-prisma-update.ts": ` const apply = Reflect.apply.bind(null); apply(db.gameVersion.update, db.gameVersion, [{ where: { id }, data: { status: "active" } }]); ` } }, { label: "Reflect.apply.call Function.prototype.call Job status write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-reflect-apply-call-function-prototype-call-status.ts": ` Reflect.apply.call(Reflect, Function.prototype.call, db.job.update, [ db.job, { where: { id }, data: { status } } ]); ` } }, { label: "computed destructured Job delegate alias write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-computed-destructured-alias.ts": ` const { ["j" + "o" + "b"]: jobs } = db; await jobs.update({ where: { id }, data: { status: "failed" } }); ` } }, { label: "assigned variable computed Job delegate write outside JobExecutionStore", reason: "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", files: { "apps/api/src/modules/projects/job-assigned-computed-model.ts": ` let modelName; modelName = "job"; await db[modelName].update({ where: { id }, data: { status: "failed" } }); ` } }, { label: "guarded Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version.ts": "await db.gameVersion.update({ where: { id }, data: { status: 'active' } });" } }, { label: "guarded optional Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-optional.ts": 'await db.gameVersion?.update({ where: { id }, data: { status: "active" } });' } }, { label: "guarded parenthesized Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-parenthesized.ts": 'await (db.gameVersion).update({ where: { id }, data: { status: "active" } });' } }, { label: "guarded delegate alias Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-alias.ts": 'const versions = db.gameVersion; await versions.update({ where: { id }, data: { status: "active" } });' } }, { label: "guarded assigned delegate alias Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-assigned-alias.ts": ` let versions; versions = db.gameVersion; await versions.update({ where: { id }, data: { status: "active" } }); ` } }, { label: "guarded assigned destructured delegate alias Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-assigned-destructured-alias.ts": ` let versions; ({ gameVersion: versions } = db); await versions.update({ where: { id }, data: { status: "active" } }); ` } }, { label: "guarded assigned computed destructured delegate alias Prisma write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-assigned-computed-destructured-alias.ts": ` let versions; ({ ["game" + "Version"]: versions } = db); await versions.update({ where: { id }, data: { status: "active" } }); ` } }, { label: "guarded Prisma bracket model write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-bracket-model.ts": 'await db["gameVersion"].update({ where: { id }, data: { status: "active" } });' } }, { label: "guarded Prisma computed bracket model write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-computed-bracket-model.ts": 'await db["game" + "Version"].update({ where: { id }, data: { status: "active" } });' } }, { label: "guarded Prisma bracket method write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/review-bracket-method.ts": 'await db.reviewRecord["update"]({ where: { id }, data: { status: "approved" } });' } }, { label: "guarded Prisma bracket model and method write outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/lifecycle-bracket-model-method.ts": 'await db["lifecycleEvent"]["delete"]({ where: { eventId } });' } }, { label: "guarded raw SQL simple update outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-update.ts": 'await db.$executeRawUnsafe("UPDATE \\"GameVersion\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "guarded parenthesized raw SQL update outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-parenthesized-update.ts": 'await (db.$executeRawUnsafe)("UPDATE \\"GameVersion\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "guarded raw SQL block-comment update through optional bracket outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-block-comment-update.ts": 'await db?.["$executeRawUnsafe"]?.("UPDATE/*x*/\\"GameVersion\\" SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "guarded raw SQL UESCAPE update through call outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-uescape-update.ts": 'await db.$executeRawUnsafe.call(db, "UPDATE U&\\"Game!0056ersion\\" UESCAPE \'!\' SET \\"status\\" = $1 WHERE id = $2", status, id);' } }, { label: "guarded raw SQL inheritance update through bound alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-bound-alias-update.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); await execRaw('UPDATE ONLY "GameVersion" * gv SET "status" = $1 WHERE gv.id = $2', status, id); ` } }, { label: "guarded raw SQL insert through plain alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/review-raw-plain-alias-insert.ts": ` const execRaw = db.$executeRawUnsafe; await execRaw('INSERT INTO "ReviewRecord" ("id") VALUES ($1)', id); ` } }, { label: "guarded raw SQL delete through assigned plain alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/lifecycle-raw-assigned-plain-alias-delete.ts": ` let execRaw; execRaw = db.$executeRawUnsafe; await execRaw('DELETE FROM "LifecycleEvent" WHERE "eventId" = $1', eventId); ` } }, { label: "guarded raw SQL merge through destructured alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/version-raw-destructured-alias-merge.ts": ` const { $executeRawUnsafe: execRaw } = db; await execRaw('MERGE INTO "GameVersion" gv USING "GameVersionPatch" p ON gv.id = p.id WHEN MATCHED THEN UPDATE SET "status" = p."status"'); ` } }, { label: "guarded raw SQL truncate through assigned bound alias outside state-transition", reason: "GUARDED_PRISMA_WRITE_OUTSIDE_STATE_TRANSITION", files: { "apps/api/src/modules/projects/review-raw-assigned-bound-alias-truncate.ts": ` let execRaw; execRaw = db.$executeRawUnsafe.bind(db); await execRaw('TRUNCATE TABLE "ReviewRecord"'); ` } }, { label: "later-phase creation-agent messages path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/creation-agent/messages/index.ts": "export const harmless = true;" } }, { label: "later-phase creation-agent messages single-file path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/creation-agent/messages.ts": "export const harmless = true;" } }, { label: "later-phase compile-game-ir module path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/compile-game-ir/index.ts": "export const harmless = true;" } }, { label: "later-phase events batch route path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/events/batch/index.ts": "export const harmless = true;" } }, { label: "later-phase events batch route content without leading slash", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/events/index.ts": 'export const route = "events/batch";' } }, { label: "later-phase events batch single-file path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/events/batch.ts": "export const harmless = true;" } }, { label: "later-phase PromptRecord app implementation content", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/projects/prompt-record.ts": "export interface PromptRecord { rawPrompt: string; }" } }, { label: "later-phase DesignSection app implementation content", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/projects/design-section.ts": "export interface DesignSection { projectId: string; payloadJson: unknown; }" } }, { label: "later-phase DesignSectionPatch app implementation content", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/projects/design-section-patch.ts": "export interface DesignSectionPatch { id: string; patch: unknown; }" } }, { label: "later-phase AIGameDesignDraft filename path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/AIGameDesignDraft.ts": "export const harmless = true;" } }, { label: "later-phase GameIR app module path", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/projects/game-ir.ts": "export const harmless = true;" } }, { label: "later-phase GameIR app implementation content", reason: "LATER_PHASE_SCOPE_LEAK", files: { "apps/api/src/modules/projects/index.ts": "class GameIRService {}" } } ]; for (const fixture of failingFixtures) { await withFixture(fixture.files, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, fixture.reason, fixture.label); }); } await withFixture( { "apps/api/prisma/schema.prisma": ` model MainCreationAgentSession { id String @id } model AgentTask { id String @id } model ReviewRecord { id String @id } model LifecycleEvent { eventId String @id } ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "S1 anchor models"); } ); await withFixture( { "apps/api/src/prisma.schema.spec.ts": ` const route = "creation-agent/messages"; await db.job.update({ where: { id }, data: { status: "failed" } }); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "historical prisma schema test later-phase leak"); assertHasReason(violations, "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", "historical prisma schema test Job write"); } ); await withFixture( { "apps/api/src/modules/state-transition/index.ts": 'import "./index.js"; export class StateTransitionService {}', "apps/api/src/modules/state-transition/self.ts": 'await import(`../state-transition/index.js`); require(`../state-transition/index.js`); const r = require; r(`../state-transition/index.js`); let rr; rr = require; rr("../state-transition/index.js");', "apps/api/src/modules/projects/index.ts": 'import "../state-transition/index.js"; await import("../state-transition/index.js"); require("../state-transition/index.js"); await import(`../state-transition/index.js`); require(`../state-transition/index.js`); await import("../state-transition" + "/index.js"); require("../state-" + "transition/index.js"); const mod = "../state-transition/index.js"; await import(mod); require(mod); const stateTransitionPath = "../state-transition/index.js"; const recursiveMod = stateTransitionPath; await import(recursiveMod); const base = "../state-transition"; const concatenatedMod = base + "/index.js"; require(concatenatedMod);' }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "allowed state-transition import paths"); } ); await withFixture( { "apps/api/src/modules/state-transition/raw-guarded.ts": ` const execRaw = db.$executeRawUnsafe.bind(db); await execRaw('UPDATE "GameVersion" SET "status" = $1 WHERE id = $2', status, id); await db.$executeRawUnsafe('TRUNCATE TABLE "ReviewRecord"'); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "allowed state-transition raw guarded write path"); } ); await withFixture( { "apps/api/src/modules/audit/audit.spec.ts": ` await expectPrismaRejectedInSavepoint( tx, () => tx.$executeRawUnsafe('UPDATE "AuditLog" SET "action" = $1 WHERE id = $2', action, id), /AuditLog is append-only/ ); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "AuditLog append-only raw SQL trigger assertion"); } ); await withFixture( { "apps/api/src/modules/projects/dynamic-select.ts": ` const tableName = process.env.READ_TABLE ?? '"Job"'; const query = "SELECT * FROM " + tableName + " WHERE id = $1"; await db.$queryRawUnsafe(query, id); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "dynamic raw SQL SELECT"); } ); await withFixture( { "apps/api/src/modules/projects/nonraw-wrapper.ts": [ "const nonRaw = (sql) => sql;", "const f1 = nonRaw;", "const f2 = f1;", "f2('DELETE FROM \"AuditLog\" WHERE id = $1');", "", "const ops = { execRaw: nonRaw };", "const { execRaw } = ops;", 'execRaw("DELETE FROM \\"AuditLog\\" WHERE id = $1");', "", "const ops2 = { execRaw: nonRaw };", "ops2.execRaw.call(null, 'DELETE FROM \"AuditLog\" WHERE id = $1');", "", "const shorthand = nonRaw;", "const opsShorthand = { shorthand };", "opsShorthand.shorthand('DELETE FROM \"AuditLog\" WHERE id = $1');", "const assignedBox = Object.assign({}, { shorthand });", "assignedBox.shorthand('DELETE FROM \"AuditLog\" WHERE id = $1');", "", "const arr = [nonRaw];", "arr[0]('UPDATE \"Job\" SET \"status\" = $1');", "const arr2 = [1, nonRaw];", "const [, arrExec] = arr2;", "arrExec('DELETE FROM \"AuditLog\" WHERE id = $1');", "let assignedArrExec;", "([, assignedArrExec] = arr2);", "assignedArrExec('DELETE FROM \"AuditLog\" WHERE id = $1');", "", "function raw() { return nonRaw; }", "raw()('DELETE FROM \"AuditLog\" WHERE id = $1');", "", "function getBox() { return { raw: nonRaw, nested: { exec: nonRaw }, arr: [nonRaw] }; }", "const { raw: boxedRaw, nested: { exec: nestedBoxedRaw } } = getBox();", "boxedRaw('DELETE FROM \"AuditLog\" WHERE id = $1');", "nestedBoxedRaw('DELETE FROM \"AuditLog\" WHERE id = $1');", "const boxedAlias = getBox().raw;", "boxedAlias('DELETE FROM \"AuditLog\" WHERE id = $1');", "getBox().arr[0]('UPDATE \"Job\" SET \"status\" = $1');", "", "function getParenBox() { return ({ raw: nonRaw, arr: [nonRaw] }); }", "getParenBox().raw('DELETE FROM \"AuditLog\" WHERE id = $1');", "getParenBox().arr[0]('UPDATE \"Job\" SET \"status\" = $1');", "", "const nested = { inner: { exec: nonRaw } };", "const { inner: { exec } } = nested;", "exec('DELETE FROM \"AuditLog\" WHERE id = $1');", "", "const rawArrow = () => nonRaw;", "rawArrow()('DELETE FROM \"AuditLog\" WHERE id = $1');", "const blockArrow = () => { return nonRaw; };", "blockArrow()('DELETE FROM \"AuditLog\" WHERE id = $1');", "const logicalNonRaw = maybe || nonRaw;", "logicalNonRaw('DELETE FROM \"AuditLog\" WHERE id = $1');", "const ternaryNonRaw = ok ? nonRaw : nonRaw;", "ternaryNonRaw('UPDATE \"Job\" SET \"status\" = $1');", "const conditionalNonRaw = ok ? nonRaw : safe;", "conditionalNonRaw('DELETE FROM \"AuditLog\" WHERE id = $1');", "const typedRaw = (): typeof nonRaw => nonRaw;", "typedRaw()('DELETE FROM \"AuditLog\" WHERE id = $1');", "const typedBox = (): { raw: typeof nonRaw } => ({ raw: nonRaw });", "typedBox().raw('UPDATE \"Job\" SET \"status\" = $1');", "", "const rawExpression = function () { return nonRaw; };", "rawExpression()('DELETE FROM \"AuditLog\" WHERE id = $1');", "", "const ordinary = { inner: { update: nonRaw } };", "const { inner: { update } } = ordinary;", 'update({ where: { id }, data: { status: "active" } });', "const deleteJob = (args) => args;", "await deleteJob({ where: { id } });", "const deleteBox = Object.assign({}, { deleteJob });", "await deleteBox.deleteJob({ where: { id } });", "const getDeleteMany = () => { return deleteJob; };", 'await getDeleteMany()({ where: { status: "running" } });', "const safeJobs = { delete: (args) => args };", "const otherJobs = { delete: (args) => args };", "const jobs = ok ? safeJobs : otherJobs;", "await jobs.delete({ where: { id } });", "const safeDelete = (args) => args;", "const otherSafeDelete = (args) => args;", "const safeDel = ok ? safeDelete : otherSafeDelete;", "await safeDel.call(db.job, { where: { id } });", "await nonRaw('DELETE FROM \"AuditLog\" WHERE id = $1', id);", 'await import(`../${"safe-module"}/index.js`);', 'const seg = "safe-module";', "await import(`../${seg}/index.js`);" ].join("\n") }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "non raw wrapper aliases"); } ); await withFixture( { "apps/api/src/modules/projects/job-cleanup.spec.ts": ` const runId = "scope-test"; await prisma.job.deleteMany({ where: { id: { startsWith: runId } } }); await db.job.deleteMany({ where: { id: { startsWith: runId } } }); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "test-only Job runId cleanup"); } ); await withFixture( { "apps/api/src/modules/projects/dynamic-select-unicode-quoted.ts": ` const tableName = process.env.READ_TABLE ?? 'U&"J\\\\006Fb"'; const query = "SELECT * FROM " + tableName + " WHERE id = $1"; await db.$queryRawUnsafe(query, id); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "dynamic raw SQL SELECT with unicode quoted identifier"); } ); await withFixture( { "apps/api/src/modules/projects/non-raw-reflect-apply.ts": ` const nonRaw = (sql) => sql; Reflect.apply(nonRaw, null, ['DELETE FROM "AuditLog" WHERE id = $1']); Function.prototype.call.call(nonRaw, null, 'DELETE FROM "AuditLog" WHERE id = $1'); const c = Function.prototype.call; const ra = Reflect.apply; c.call(nonRaw, null, 'DELETE FROM "AuditLog" WHERE id = $1', id); ra(nonRaw, null, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]); Reflect.apply.call(Reflect, nonRaw, null, ["DELETE FROM \\"AuditLog\\" WHERE id = $1", id]); const boundReflectApply = Reflect.apply.bind(Reflect); boundReflectApply(nonRaw, null, ["UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id]); const nullBoundReflectApply = Reflect.apply.bind(null); nullBoundReflectApply(nonRaw, null, ["UPDATE \\"Job\\" SET \\"status\\" = $1 WHERE id = $2", status, id]); const boundCall = Function.prototype.call.bind(Function.prototype.call); boundCall(nonRaw, null, "DELETE FROM \\"AuditLog\\" WHERE id = $1", id); Function.prototype.call.apply(nonRaw, [null, 'DELETE FROM "AuditLog" WHERE id = $1', id]); Function.prototype.apply.apply(nonRaw, [null, ['UPDATE "Job" SET "status" = $1 WHERE id = $2', status, id]]); Function.prototype.call.bind(nonRaw)(null, 'DELETE FROM "AuditLog" WHERE id = $1'); Function.prototype.apply.bind(nonRaw)(null, ['UPDATE "Job" SET "status" = $1']); const boundNonRaw = Function.prototype.call.bind(nonRaw); boundNonRaw(null, 'DELETE FROM "AuditLog" WHERE id = $1'); Function.prototype.call?.bind(nonRaw)(null, 'DELETE FROM "AuditLog" WHERE id = $1'); Function.prototype["call"].bind(nonRaw)(null, 'DELETE FROM "AuditLog" WHERE id = $1'); Reflect["apply"](nonRaw, null, ['DELETE FROM "AuditLog" WHERE id = $1']); Reflect["ap" + "ply"](nonRaw, null, ['UPDATE "Job" SET "status" = $1']); const bracketBoundCall = Function.prototype["call"].bind(Function.prototype["call"]); bracketBoundCall(nonRaw, null, 'DELETE FROM "AuditLog" WHERE id = $1'); const optionalBoundExec = Function.prototype.call.bind(nonRaw); const optionalBoundReflectApply = Reflect.apply.bind(Reflect); const optionalPlainReflectApply = Reflect.apply; bracketBoundCall?.(nonRaw, null, 'DELETE FROM "AuditLog" WHERE id = $1'); optionalBoundExec?.(null, 'DELETE FROM "AuditLog" WHERE id = $1'); optionalBoundReflectApply?.(nonRaw, null, ['UPDATE "Job" SET "status" = $1']); optionalPlainReflectApply?.(nonRaw, null, ['DELETE FROM "AuditLog" WHERE id = $1']); nonRaw["call"](null, 'DELETE FROM "AuditLog" WHERE id = $1'); nonRaw["apply"](null, ['UPDATE "Job" SET "status" = $1']); nonRaw["bind"](null)('DELETE FROM "AuditLog" WHERE id = $1'); const f1 = nonRaw; const f2 = f1; f2('DELETE FROM "AuditLog" WHERE id = $1'); const ops = { execRaw: nonRaw }; ops.execRaw.call(null, 'DELETE FROM "AuditLog" WHERE id = $1'); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "non-raw Reflect.apply and Function.prototype.call"); } ); await withFixture( { "apps/api/prisma/migrations/20260602000002_job_schema_ddl/migration.sql": ` CREATE TABLE "Job" ("id" TEXT PRIMARY KEY, "status" TEXT NOT NULL); CREATE INDEX "Job_status_idx" ON "Job" ("status"); ` }, async (root) => { const violations = await findS1ScopeViolations(root); assertNoViolations(violations, "Job schema DDL migration"); } ); await withFixture( { "apps/api/src/modules/creation-agent/messages/index.ts": ` export type PromptRecord = { id: string }; export type AIGameDesignDraft = { id: string }; export type DesignSection = { id: string }; export type DesignSectionPatch = { id: string }; export type GameIR = { id: string }; export type GameIRArtifact = { id: string }; export const route = "creation-agent/messages"; export const compileRoute = "compile-game-ir"; ` }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertNoViolations(violations, "S2 approved creation workbench terms"); } ); await withFixture( { "apps/api/src/modules/game-ir/index.ts": ` export type GameIR = { id: string }; export type GameIRArtifact = { id: string }; export const compiler = "compile-game-ir"; `, "packages/shared-contracts/src/game-ir.ts": ` export type DesignSection = { id: string }; export type DesignSectionPatch = { id: string }; ` }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertNoViolations(violations, "S2 approved GameIR and shared-contracts paths"); } ); await withFixture( { "apps/api/src/prisma.schema.spec.ts": ` export type PromptRecord = { id: string }; export type AIGameDesignDraft = { id: string }; export type DesignSection = { id: string }; export type DesignSectionPatch = { id: string }; ` }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertNoViolations(violations, "S2 approved Prisma schema contract test terms"); } ); await withFixture( { "apps/api/src/prisma.schema.spec.ts": ` export type PromptRecord = { id: string }; await db.job.update({ where: { id }, data: { status: "failed" } }); ` }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason( violations, "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", "S2 Prisma schema contract test still keeps JobExecutionStore safety scanner" ); } ); await withFixture( { "apps/api/src/modules/simulation/game-ir.ts": "export interface GameIR { id: string }" }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "S2 blocks GameIR in simulation path"); } ); await withFixture( { "apps/api/src/modules/simulation/design-section.ts": "export interface DesignSection { id: string }" }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "S2 blocks DesignSection in simulation path"); } ); await withFixture( { "apps/api/src/modules/web-runtime/game-ir.ts": "export interface GameIR { id: string }", "packages/shared-contracts/src/conversion/game-ir.ts": "export interface GameIRArtifact { id: string }" }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "S2 blocks approved terms in denied later-phase paths"); } ); await withFixture( { "apps/api/src/modules/web-runtime/index.ts": "export interface MiniGameProject { id: string }", "apps/api/src/modules/events/batch.ts": 'export const route = "/events/batch";' }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "S2 still blocks S3-S8 scope leaks"); } ); await withFixture( { "apps/api/src/modules/creation-agent/messages/job.ts": 'await db.job.update({ where: { id }, data: { status: "failed" } });' }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason( violations, "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", "S2 keeps S1 JobExecutionStore safety scanner" ); } ); await withFixture( { "apps/api/src/modules/creation-agent/session.ts": ` export class MainCreationAgentSessionService {} export class AgentTaskRunner {} ` }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertNoViolations(violations, "S2 approved S1 creation agent anchors"); } ); await withFixture( { "apps/api/src/modules/creation-agent/review.ts": "export class ReviewRecordWorkflow {}" }, async (root) => { const violations = await findScopeViolations(root, "S2"); assertHasReason(violations, "S1_ANCHOR_SCOPE_MISUSE", "S2 still blocks review/lifecycle anchors"); } ); await withFixture( { "apps/api/src/modules/game-config/index.ts": "export type GameConfig = { id: string };", "apps/api/src/modules/game-logic/index.ts": "export type GameLogicModule = { id: string };", "apps/api/src/modules/simulation/index.ts": "export type SimulationRuleSet = { id: string };" }, async (root) => { const s1Violations = await findScopeViolations(root, "S1"); const s2Violations = await findScopeViolations(root, "S2"); assertHasReason(s1Violations, "LATER_PHASE_SCOPE_LEAK", "S1 still blocks S3 approved terms and paths"); assertHasReason(s2Violations, "LATER_PHASE_SCOPE_LEAK", "S2 still blocks S3 approved terms and paths"); } ); await withFixture( { "apps/api/src/modules/game-config/index.ts": ` export type GameConfig = { id: string }; export type RuntimeSdkContract = { version: string }; export const route = "compile-game-config"; `, "apps/api/src/modules/game-logic/index.ts": ` export type GameLogicModule = { id: string }; export type SimulationRuleSet = { id: string }; export type NpcBehaviorGraph = { id: string }; export const route = "validate-game-logic"; `, "apps/api/src/modules/simulation/index.ts": ` export type ValidationReport = { id: string }; export const moduleName = "game-config/game-logic/simulation"; `, "packages/shared-contracts/src/game-config.ts": "export type GameConfig = { id: string };", "packages/shared-contracts/src/game-logic-module.ts": "export type GameLogicModule = { id: string };", "packages/shared-contracts/src/runtime-sdk-contract.ts": ` export type RuntimeSdkContract = { version: string }; export type LifecycleEventDto = { type: string }; export const validateLifecycleEventDto = (event) => event; ` }, async (root) => { const violations = await findScopeViolations(root, "S3"); assertNoViolations(violations, "S3 approved simulation slice terms and paths"); } ); await withFixture( { "packages/shared-contracts/src/runtime-sdk-contract.ts": ` export type LifecycleEvent = { id: string }; ` }, async (root) => { const violations = await findScopeViolations(root, "S3"); assertHasReason(violations, "S1_ANCHOR_SCOPE_MISUSE", "S3 only allows LifecycleEventDto, not S1 LifecycleEvent anchor"); } ); await withFixture( { "apps/api/src/modules/web-runtime/index.ts": ` export type GamePackage = { id: string }; export type MiniGameProject = { id: string }; export type MiniGameCodeConversion = { id: string }; export type ConversionReport = { id: string }; export type FeedItem = { id: string }; export type GameDailyStats = { id: string }; export type CreatorDailyStats = { id: string }; export const routes = ["/events/batch", "publish-review-feed", "channel-readiness", "deploy-readiness"]; ` }, async (root) => { const violations = await findScopeViolations(root, "S3"); assertHasReason(violations, "LATER_PHASE_SCOPE_LEAK", "S3 still blocks S4-S8 scope leaks"); } ); await withFixture( { "apps/api/src/modules/game-config/job.ts": 'await db.job.update({ where: { id }, data: { status: "failed" } });', "apps/api/src/modules/game-logic/audit.ts": "await db.auditLog.delete({ where: { id } });", "apps/api/src/modules/simulation/raw.ts": ` const tableName = process.env.JOB_TABLE ?? '"Job"'; const fieldName = '"status"'; const query = "UPDATE " + tableName + " SET " + fieldName + " = $1 WHERE id = $2"; await db.$executeRawUnsafe(query, status, id); ` }, async (root) => { const violations = await findScopeViolations(root, "S3"); assertHasReason(violations, "JOB_STATUS_WRITE_OUTSIDE_EXECUTION_STORE", "S3 keeps JobExecutionStore safety scanner"); assertHasReason(violations, "AUDIT_UPDATE_DELETE_FORBIDDEN", "S3 keeps AuditLog append-only scanner"); assertHasReason(violations, "DYNAMIC_RAW_SQL_WRITE_FORBIDDEN", "S3 keeps dynamic raw SQL scanner"); } ); } try { const phase = parsePhaseArg(process.argv.slice(2)); await runSelfTests(); const violations = await findScopeViolations(process.cwd(), phase); if (violations.length > 0) { console.error(`${phase} scope check failed:`); for (const violation of violations) { console.error(`- ${formatViolation(violation)}`); } process.exitCode = 1; } else { console.log(`${phase} scope check passed.`); } } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }