import { readFile, readdir } from "node:fs/promises"; import path from "node:path"; const root = path.resolve("src"); const forbiddenSdk = /\b(?:new\s+Client|useClient|ClientProvider|useStream)\b/; const forbiddenBrowserConfig = /\b(?:deploymentUrl|langsmithApiKey|NEXT_PUBLIC_LANGSMITH_API_KEY)\b/; const workspaceRoutes = [ "app/api/workspace/route.ts", "app/api/workspace/file/route.ts", "app/api/workspace/upload/route.ts", "app/api/workspace/download/route.ts", ]; async function sourceFiles(directory) { const entries = await readdir(directory, { withFileTypes: true }); const files = []; for (const entry of entries) { const absolute = path.join(directory, entry.name); if (entry.isDirectory()) files.push(...(await sourceFiles(absolute))); else if (/\.(?:ts|tsx)$/.test(entry.name)) files.push(absolute); } return files; } const violations = []; for (const file of await sourceFiles(root)) { const relative = path.relative(root, file).replaceAll(path.sep, "/"); // Server-side BFF clients are intentionally the only LangGraph SDK clients. if (relative.startsWith("lib/server/") || relative.startsWith("app/api/")) continue; const source = await readFile(file, "utf8"); if (forbiddenSdk.test(source)) violations.push(`${relative} (LangGraph SDK)`); if (forbiddenBrowserConfig.test(source)) { violations.push(`${relative} (deployment credential or URL)`); } } if (violations.length) { throw new Error(`Browser BFF boundary violation: ${violations.join(", ")}`); } for (const route of workspaceRoutes) { const source = await readFile(path.join(root, route), "utf8"); if (!source.includes("resolveConversationWorkspace")) { throw new Error(`Workspace route is not scope-resolved: ${route}`); } } console.log("Verified browser BFF boundary and conversation-scoped workspace routes.");