Files

284 lines
9.3 KiB
JavaScript

#!/usr/bin/env node
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const backendRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const root = dirname(backendRoot);
const failures = [];
const notes = [];
const read = (relativePath) => readFileSync(join(root, relativePath), "utf8");
const exists = (relativePath) => existsSync(join(root, relativePath));
const requireFile = (relativePath) => {
if (!exists(relativePath)) failures.push(`Missing required backend contract file: ${relativePath}`);
};
const requireIncludes = (relativePath, markers) => {
if (!exists(relativePath)) {
failures.push(`Missing required file for marker audit: ${relativePath}`);
return;
}
const text = read(relativePath);
for (const marker of markers) {
if (!text.includes(marker)) failures.push(`${relativePath} is missing marker: ${marker}`);
}
};
const rejectIncludes = (relativePath, markers) => {
if (!exists(relativePath)) return;
const text = read(relativePath);
for (const marker of markers) {
if (text.includes(marker)) failures.push(`${relativePath} still contains stale backend status marker: ${marker}`);
}
};
const routeRegistrations = [
"registerHealthRoutes",
"registerAuthRoutes",
"registerEntraAuthRoutes",
"registerProjectRoutes",
"registerFeatureRoutes",
"registerEvidenceRoutes",
"registerContextPackRoutes",
"registerPromptRoutes",
"registerCheckRoutes",
"registerBrainstormRoutes",
"registerBuildRoutes",
"registerBlueprintRoutes",
"registerRegressionRoutes",
"registerSnapshotRoutes",
"registerInsightRoutes",
"registerRelationshipRoutes",
"registerDocumentationRoutes",
"registerKnowledgeRoutes",
"registerInitiativeRoutes",
"registerMemoryRuleRoutes",
"registerCaptureRoutes",
"registerReviewQueueRoutes",
"registerRuntimePersistenceRoutes",
"registerP7RuntimeRoutes",
"registerAIActionRoutes",
"registerSystemReadinessRoutes",
];
const routeFiles = [
"backend/src/routes/health.ts",
"backend/src/routes/auth.ts",
"backend/src/routes/entraAuth.ts",
"backend/src/routes/projects.ts",
"backend/src/routes/features.ts",
"backend/src/routes/evidence.ts",
"backend/src/routes/contextPacks.ts",
"backend/src/routes/prompts.ts",
"backend/src/routes/checks.ts",
"backend/src/routes/brainstorms.ts",
"backend/src/routes/builds.ts",
"backend/src/routes/blueprints.ts",
"backend/src/routes/regressions.ts",
"backend/src/routes/snapshots.ts",
"backend/src/routes/insights.ts",
"backend/src/routes/relationships.ts",
"backend/src/routes/documentation.ts",
"backend/src/routes/knowledge.ts",
"backend/src/routes/initiatives.ts",
"backend/src/routes/memoryRules.ts",
"backend/src/routes/capture.ts",
"backend/src/routes/reviewQueue.ts",
"backend/src/routes/runtimePersistence.ts",
"backend/src/routes/p7Runtime.ts",
"backend/src/routes/aiActions.ts",
"backend/src/routes/systemReadiness.ts",
];
const endpointDocs = [
"GET /api/health",
"GET /api/auth/entra/config",
"GET /api/auth/entra/start",
"GET /api/auth/entra/callback",
"GET /api/auth/entra/status",
"POST /api/auth/entra/link/start",
"DELETE /api/auth/entra/identity",
"GET /api/projects",
"POST /api/projects",
"GET /api/projects/:projectId",
"PATCH /api/projects/:projectId",
"GET /api/projects/:projectId/workspace",
"PATCH /api/projects/:projectId/workspace/active-context-pack",
"GET /api/projects/:projectId/dna",
"GET /api/projects/:projectId/feature-groups",
"GET /api/projects/:projectId/features",
"GET /api/projects/:projectId/evidence",
"GET /api/projects/:projectId/context-packs",
"POST /api/projects/:projectId/prompts/generate",
"GET /api/projects/:projectId/checks",
"GET /api/projects/:projectId/builds",
"GET /api/projects/:projectId/regressions",
"GET /api/projects/:projectId/snapshots",
"GET /api/projects/:projectId/insights",
"GET /api/projects/:projectId/relationships",
"GET /api/projects/:projectId/knowledge",
"GET /api/projects/:projectId/memory-rules",
"GET /api/projects/:projectId/capture-items",
"GET /api/projects/:projectId/review-queue",
"GET /api/projects/:projectId/ai-actions",
"GET /api/projects/:projectId/accepted-truth",
"GET /api/projects/:projectId/project-updates",
"GET /api/projects/:projectId/operating-log",
"GET /api/projects/:projectId/current-state",
"GET /api/projects/:projectId/handoff-bundles",
"GET /api/projects/:projectId/codex-tasks",
"GET /api/system/readiness",
];
const smokeScripts = [
"smoke:p2",
"smoke:p3",
"smoke:p4",
"smoke:p5",
"smoke:p6",
"smoke:p7",
"smoke:p8",
"smoke:fixtures",
"smoke:runtime-suite",
"smoke:database-suite",
"smoke:system-readiness",
"smoke:blueprints",
"smoke:brainstorms",
"smoke:initiatives",
];
for (const file of [
"backend/src/app.ts",
"backend/src/config/env.ts",
"backend/src/utils/envelope.ts",
"backend/src/plugins/errorHandler.ts",
"backend/src/plugins/requestId.ts",
"backend/src/db/prisma.ts",
"backend/prisma/schema.prisma",
"backend/prisma/seed.ts",
"backend/package.json",
"docs/REAL_BACKEND_ENDPOINT_INDEX.md",
"docs/PROJECTBRAIN_STATUS_AND_GATES.md",
"docs/BACKEND_RELEASE_COMPLETION_PASS.md",
]) {
requireFile(file);
}
for (const routeFile of routeFiles) requireFile(routeFile);
requireIncludes("backend/src/app.ts", routeRegistrations);
requireIncludes("backend/src/config/env.ts", [
"PROJECTBRAIN_DATA_SOURCE",
"DATABASE_URL is required in production",
"DATABASE_URL is required when PROJECTBRAIN_DATA_SOURCE=database",
]);
requireIncludes("backend/src/utils/envelope.ts", ["successEnvelope", "errorEnvelope", "requestId", "timestamp"]);
requireIncludes("backend/src/db/prisma.ts", ["PrismaClient", "getPrismaClient", "prisma"]);
requireIncludes("backend/package.json", ["audit:contract", ...smokeScripts]);
requireIncludes("package.json", ["audit:backend-contract", "audit:all"]);
requireIncludes("docs/REAL_BACKEND_ENDPOINT_INDEX.md", endpointDocs);
requireIncludes("docs/PROJECTBRAIN_STATUS_AND_GATES.md", [
"PostgreSQL production path",
"Gate 3 — Post-merge exact-SHA validation",
"Gate 4 — Runnerless deployment receipt",
"Gate 5 — Runtime verification",
]);
requireIncludes("docs/BACKEND_RELEASE_COMPLETION_PASS.md", [
"Backend Release Completion Pass",
"Fixture mode",
"Database mode",
"Database-only Routes",
"Release Gates",
]);
rejectIncludes("docs/REAL_BACKEND_ENDPOINT_INDEX.md", [
"mostly fixture-backed",
"P2 database parity is prepared",
"must be locally smoke-tested before P3 begins",
]);
rejectIncludes("docs/PROJECTBRAIN_STATUS_AND_GATES.md", [
"P2 DB parity prepared but not runtime-smoked locally yet",
"Required before P3",
"Can start only after Gate 2",
"Run P2 database smoke locally. If green, proceed to P3",
]);
requireIncludes("backend/src/routes/brainstorms.ts", ["DATABASE_MODE_REQUIRED", "PROJECTBRAIN_DATA_SOURCE=database"]);
requireIncludes("backend/src/routes/initiatives.ts", ["DATABASE_MODE_REQUIRED", "PROJECTBRAIN_DATA_SOURCE=database"]);
requireIncludes("backend/src/routes/documentation.ts", ["DATABASE_MODE_REQUIRED", "PROJECTBRAIN_DATA_SOURCE=database"]);
for (const file of [
"backend/src/routes/projects.ts",
"backend/src/routes/features.ts",
"backend/src/routes/evidence.ts",
"backend/src/routes/contextPacks.ts",
"backend/src/routes/prompts.ts",
"backend/src/routes/capture.ts",
"backend/src/routes/reviewQueue.ts",
"backend/src/routes/runtimePersistence.ts",
"backend/src/routes/p7Runtime.ts",
"backend/src/routes/knowledge.ts",
"backend/src/routes/memoryRules.ts",
"backend/src/routes/blueprints.ts",
]) {
requireIncludes(file, ["dataSource", "database"]);
}
requireIncludes("backend/scripts/smoke-fixture-api.mjs", [
"R1 health",
"R2 projects/workspace/DNA",
"R3 features/evidence",
"R4 context packs/prompts",
"R5 checks/review",
"R6 builds/regressions/snapshots",
"R7 knowledge/memory/relationships/insights",
"R8 capture/review queue/AI actions",
"error envelopes",
]);
const migrationsPath = join(root, "backend/prisma/migrations");
if (!existsSync(migrationsPath)) {
failures.push("Missing backend/prisma/migrations");
} else {
const migrationCount = readdirSync(migrationsPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
if (migrationCount < 8) failures.push(`Expected at least 8 backend migrations, found ${migrationCount}`);
else notes.push(`Migrations present: ${migrationCount}`);
}
requireIncludes("backend/prisma/schema.prisma", [
"model Project",
"model Feature",
"model Evidence",
"model ContextPack",
"model ReviewQueueItem",
"model CaptureItem",
"model AcceptedTruthItem",
"model ProjectUpdate",
"model OperatingLogEntry",
"model CurrentStateReport",
"model HandoffBundle",
"model CodexTask",
"model DocumentationItem",
"model KnowledgeItem",
"model MemoryRule",
"model BlueprintGraph",
"model BrainstormSession",
"model Initiative",
"model IdempotencyRecord",
]);
console.log("ProjectBrain backend contract audit");
console.log("===================================");
for (const note of notes) console.log(`INFO ${note}`);
if (failures.length) {
console.log("\nFailures:");
for (const failure of failures) console.log(`- ${failure}`);
process.exit(1);
}
console.log("\nPASS: backend contract surface, docs and release gates are aligned.");