import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() interface CheckResult { name: string passed: boolean details: string } async function runChecks(): Promise { const results: CheckResult[] = [] // 1. No orphan ProjectStageState (every PSS references valid project, track, stage) const orphanStates = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "ProjectStageState" pss WHERE NOT EXISTS (SELECT 1 FROM "Project" p WHERE p.id = pss."projectId") OR NOT EXISTS (SELECT 1 FROM "Track" t WHERE t.id = pss."trackId") OR NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = pss."stageId") ` const orphanCount = Number(orphanStates[0]?.count ?? 0) results.push({ name: 'No orphan ProjectStageState', passed: orphanCount === 0, details: orphanCount === 0 ? 'All PSS records reference valid entities' : `Found ${orphanCount} orphan records`, }) // 2. Every project has at least one stage state const projectsWithoutState = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "Project" p WHERE NOT EXISTS (SELECT 1 FROM "ProjectStageState" pss WHERE pss."projectId" = p.id) ` const noStateCount = Number(projectsWithoutState[0]?.count ?? 0) const totalProjects = await prisma.project.count() results.push({ name: 'Every project has at least one stage state', passed: noStateCount === 0, details: noStateCount === 0 ? `All ${totalProjects} projects have stage states` : `${noStateCount} projects missing stage states`, }) // 3. No duplicate active states per (project, track, stage) const duplicateStates = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM ( SELECT "projectId", "trackId", "stageId", COUNT(*) as cnt FROM "ProjectStageState" WHERE "exitedAt" IS NULL GROUP BY "projectId", "trackId", "stageId" HAVING COUNT(*) > 1 ) dupes ` const dupeCount = Number(duplicateStates[0]?.count ?? 0) results.push({ name: 'No duplicate active states per (project, track, stage)', passed: dupeCount === 0, details: dupeCount === 0 ? 'No duplicates found' : `Found ${dupeCount} duplicate active states`, }) // 4. All transitions stay within same pipeline const crossPipelineTransitions = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "StageTransition" st JOIN "Stage" sf ON sf.id = st."fromStageId" JOIN "Track" tf ON tf.id = sf."trackId" JOIN "Stage" sto ON sto.id = st."toStageId" JOIN "Track" tt ON tt.id = sto."trackId" WHERE tf."pipelineId" != tt."pipelineId" ` const crossCount = Number(crossPipelineTransitions[0]?.count ?? 0) results.push({ name: 'All transitions stay within same pipeline', passed: crossCount === 0, details: crossCount === 0 ? 'All transitions are within pipeline' : `Found ${crossCount} cross-pipeline transitions`, }) // 5. Stage sortOrder unique per track const duplicateSortOrders = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM ( SELECT "trackId", "sortOrder", COUNT(*) as cnt FROM "Stage" GROUP BY "trackId", "sortOrder" HAVING COUNT(*) > 1 ) dupes ` const dupeSortCount = Number(duplicateSortOrders[0]?.count ?? 0) results.push({ name: 'Stage sortOrder unique per track', passed: dupeSortCount === 0, details: dupeSortCount === 0 ? 'All sort orders unique' : `Found ${dupeSortCount} duplicate sort orders`, }) // 6. Track sortOrder unique per pipeline const duplicateTrackOrders = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM ( SELECT "pipelineId", "sortOrder", COUNT(*) as cnt FROM "Track" GROUP BY "pipelineId", "sortOrder" HAVING COUNT(*) > 1 ) dupes ` const dupeTrackCount = Number(duplicateTrackOrders[0]?.count ?? 0) results.push({ name: 'Track sortOrder unique per pipeline', passed: dupeTrackCount === 0, details: dupeTrackCount === 0 ? 'All track orders unique' : `Found ${dupeTrackCount} duplicate track orders`, }) // 7. Every Pipeline has at least one Track; every Track has at least one Stage const emptyPipelines = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "Pipeline" p WHERE NOT EXISTS (SELECT 1 FROM "Track" t WHERE t."pipelineId" = p.id) ` const emptyPipelineCount = Number(emptyPipelines[0]?.count ?? 0) const emptyTracks = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "Track" t WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s."trackId" = t.id) ` const emptyTrackCount = Number(emptyTracks[0]?.count ?? 0) results.push({ name: 'Every Pipeline has Tracks; every Track has Stages', passed: emptyPipelineCount === 0 && emptyTrackCount === 0, details: emptyPipelineCount === 0 && emptyTrackCount === 0 ? 'All pipelines have tracks and all tracks have stages' : `${emptyPipelineCount} empty pipelines, ${emptyTrackCount} empty tracks`, }) // 8. RoutingRule destinations reference valid tracks in same pipeline const badRoutingRules = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "RoutingRule" rr WHERE rr."destinationTrackId" IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM "Track" t WHERE t.id = rr."destinationTrackId" AND t."pipelineId" = rr."pipelineId" ) ` const badRouteCount = Number(badRoutingRules[0]?.count ?? 0) results.push({ name: 'RoutingRule destinations reference valid tracks in same pipeline', passed: badRouteCount === 0, details: badRouteCount === 0 ? 'All routing rules reference valid destination tracks' : `Found ${badRouteCount} routing rules with invalid destinations`, }) // 9. LiveProgressCursor references valid stage const badCursors = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "LiveProgressCursor" lpc WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = lpc."stageId") ` const badCursorCount = Number(badCursors[0]?.count ?? 0) results.push({ name: 'LiveProgressCursor references valid stage', passed: badCursorCount === 0, details: badCursorCount === 0 ? 'All cursors reference valid stages' : `Found ${badCursorCount} cursors with invalid stage references`, }) // 10. Cohort references valid stage const badCohorts = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "Cohort" c WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = c."stageId") ` const badCohortCount = Number(badCohorts[0]?.count ?? 0) results.push({ name: 'Cohort references valid stage', passed: badCohortCount === 0, details: badCohortCount === 0 ? 'All cohorts reference valid stages' : `Found ${badCohortCount} cohorts with invalid stage references`, }) // 11. Every EvaluationForm has a valid stageId const badEvalForms = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "EvaluationForm" ef WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = ef."stageId") ` const badFormCount = Number(badEvalForms[0]?.count ?? 0) results.push({ name: 'Every EvaluationForm references valid stage', passed: badFormCount === 0, details: badFormCount === 0 ? 'All evaluation forms reference valid stages' : `Found ${badFormCount} forms with invalid stage references`, }) // 12. Every FileRequirement has a valid stageId const badFileReqs = await prisma.$queryRaw<{ count: bigint }[]>` SELECT COUNT(*) as count FROM "FileRequirement" fr WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = fr."stageId") ` const badFileReqCount = Number(badFileReqs[0]?.count ?? 0) results.push({ name: 'Every FileRequirement references valid stage', passed: badFileReqCount === 0, details: badFileReqCount === 0 ? 'All file requirements reference valid stages' : `Found ${badFileReqCount} file requirements with invalid stage references`, }) // 13. Count validation const projectCountResult = await prisma.project.count() const stageCount = await prisma.stage.count() const trackCount = await prisma.track.count() const pipelineCount = await prisma.pipeline.count() const pssCount = await prisma.projectStageState.count() results.push({ name: 'Count validation', passed: projectCountResult > 0 && stageCount > 0 && trackCount > 0, details: `Pipelines: ${pipelineCount}, Tracks: ${trackCount}, Stages: ${stageCount}, Projects: ${projectCountResult}, StageStates: ${pssCount}`, }) return results } async function main() { console.log('🔍 Running integrity checks...\n') const results = await runChecks() let allPassed = true for (const result of results) { const icon = result.passed ? '✅' : '❌' console.log(`${icon} ${result.name}`) console.log(` ${result.details}\n`) if (!result.passed) allPassed = false } console.log('='.repeat(50)) if (allPassed) { console.log('✅ All integrity checks passed!') } else { console.log('❌ Some integrity checks failed!') process.exit(1) } } main() .catch((e) => { console.error('❌ Integrity check failed:', e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })