diff --git a/src/mcpd/src/bootstrap/system-project.ts b/src/mcpd/src/bootstrap/system-project.ts index 444902e..2cb093e 100644 --- a/src/mcpd/src/bootstrap/system-project.ts +++ b/src/mcpd/src/bootstrap/system-project.ts @@ -7,12 +7,12 @@ import type { PrismaClient } from '@prisma/client'; -/** Well-known owner ID for system-managed resources. */ -export const SYSTEM_OWNER_ID = 'system'; - /** Well-known project name for system prompts. */ export const SYSTEM_PROJECT_NAME = 'mcpctl-system'; +/** Well-known email for the system user. */ +const SYSTEM_USER_EMAIL = 'system@mcpctl.local'; + interface SystemPromptDef { name: string; priority: number; @@ -62,6 +62,18 @@ This will load relevant project context, policies, and guidelines tailored to yo * Uses upserts so this is safe to call on every startup. */ export async function bootstrapSystemProject(prisma: PrismaClient): Promise { + // Ensure a system user exists (needed as project owner) + const systemUser = await prisma.user.upsert({ + where: { email: SYSTEM_USER_EMAIL }, + create: { + email: SYSTEM_USER_EMAIL, + name: 'System', + passwordHash: '!locked', // Cannot login — not a real password hash + role: 'USER', + }, + update: {}, + }); + // Upsert the system project const project = await prisma.project.upsert({ where: { name: SYSTEM_PROJECT_NAME }, @@ -71,7 +83,7 @@ export async function bootstrapSystemProject(prisma: PrismaClient): Promise ({ + id: 'sys-user-id', + email: 'system@mcpctl.local', + name: 'System', + })), + }, project: { upsert: vi.fn(async (args: { where: { name: string }; create: Record; update: Record }) => ({ id: 'sys-proj-id', @@ -35,6 +42,20 @@ describe('bootstrapSystemProject', () => { prisma = mockPrisma(); }); + it('creates a system user via upsert', async () => { + await bootstrapSystemProject(prisma); + + expect(prisma.user.upsert).toHaveBeenCalledWith( + expect.objectContaining({ + where: { email: 'system@mcpctl.local' }, + create: expect.objectContaining({ + email: 'system@mcpctl.local', + name: 'System', + }), + }), + ); + }); + it('creates the mcpctl-system project via upsert', async () => { await bootstrapSystemProject(prisma); @@ -43,7 +64,7 @@ describe('bootstrapSystemProject', () => { where: { name: SYSTEM_PROJECT_NAME }, create: expect.objectContaining({ name: SYSTEM_PROJECT_NAME, - ownerId: SYSTEM_OWNER_ID, + ownerId: 'sys-user-id', gated: false, }), update: {},