Files
neta/scripts/phase7-backend-boundary.mjs

128 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
const runtimeRoots = [
"app/(dashboard)/chat",
"app/(dashboard)/business",
"app/api/chat",
"app/api/finance-analysis",
"app/api/project-risk",
];
const files = runtimeRoots
.flatMap(walk)
.filter((file) => /\.(ts|tsx)$/.test(file));
const violations = [];
for (const file of files) {
const content = fs.readFileSync(path.join(process.cwd(), file), "utf8");
if (/[@/]lib\/supabase|createServiceRoleClient|\bsupabase\b/i.test(content)) {
violations.push(`${file}: Supabase runtime reference`);
}
if (/NEXT_PUBLIC_SUPABASE|SUPABASE_SERVICE_ROLE/.test(content)) {
violations.push(`${file}: Supabase environment dependency`);
}
if (/\blocalStorage\b/.test(content)) {
violations.push(`${file}: browser localStorage dependency`);
}
}
for (const route of [
"app/api/chat/route.ts",
"app/api/finance-analysis/route.ts",
"app/api/project-risk/route.ts",
]) {
const content = fs.readFileSync(path.join(process.cwd(), route), "utf8");
if (/body\.(apiKey|provider)|create(OpenAI|Groq|GoogleGenerativeAI)/.test(content)) {
violations.push(`${route}: provider or secret is selected from the route/browser boundary`);
}
}
const chatPage = fs.readFileSync(
path.join(process.cwd(), "app/(dashboard)/chat/page.tsx"),
"utf8",
);
if (/\bapiKey\b|\bprovider\b/.test(chatPage)) {
violations.push("app/(dashboard)/chat/page.tsx: AI secret/provider leaked to browser code");
}
const chatRoute = fs.readFileSync(
path.join(process.cwd(), "app/api/chat/route.ts"),
"utf8",
);
for (const transportField of ['id:', 'trigger:', 'messageId:']) {
assert.ok(
chatRoute.includes(transportField),
`Chat request schema must accept AI SDK v6 transport field ${transportField}`,
);
}
for (const diagnosticMarker of [
"describeRequestIssues",
"x-neta-error-code",
"geçerli bir JSON gövdesi",
"her mesaj id, role ve parts",
]) {
assert.ok(
chatRoute.includes(diagnosticMarker),
`Chat route is missing diagnostic marker: ${diagnosticMarker}`,
);
}
const aiProvider = fs.readFileSync(
path.join(process.cwd(), "server/ai/provider.ts"),
"utf8",
);
for (const providerDiagnostic of [
"APICallError",
"API anahtarını reddetti",
"kullanım limiti aşıldı",
"modeli sağlayıcıda bulunamadı",
]) {
assert.ok(
aiProvider.includes(providerDiagnostic),
`AI provider diagnostics are missing: ${providerDiagnostic}`,
);
}
assert.deepEqual(
violations,
[],
`Phase 7 backend boundary violations:\n${violations.join("\n")}`,
);
for (const required of [
"server/ai/context.ts",
"server/ai/provider.ts",
"server/ai/responses.ts",
"app/(dashboard)/chat/actions.ts",
"scripts/phase7-domain-smoke.ts",
]) {
assert.ok(fs.existsSync(path.join(process.cwd(), required)), `Missing Phase 7 artifact: ${required}`);
}
const allRuntimeSources = [
...walk("app"),
...walk("server"),
].filter((file) => /\.(ts|tsx)$/.test(file));
for (const file of allRuntimeSources) {
const content = fs.readFileSync(path.join(process.cwd(), file), "utf8");
assert.doesNotMatch(
content,
/from\s+["'][^"']*lib\/ai\/embeddings["']/,
`Legacy Supabase embeddings helper is imported at runtime by ${file}`,
);
}
console.log(`Phase 7 backend boundary passed (${files.length} files scanned).`);
function walk(relativePath) {
const absolutePath = path.join(process.cwd(), relativePath);
if (!fs.existsSync(absolutePath)) return [];
const stat = fs.statSync(absolutePath);
if (stat.isFile()) return [relativePath];
return fs.readdirSync(absolutePath, { withFileTypes: true }).flatMap((entry) => {
const child = path.join(relativePath, entry.name);
return entry.isDirectory() ? walk(child) : [child];
});
}