284 lines
9.3 KiB
JavaScript
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.");
|