From f388c0992427c584cf6ce704153071af8aa58c5c Mon Sep 17 00:00:00 2001 From: Michal Date: Wed, 25 Feb 2026 23:27:59 +0000 Subject: [PATCH] fix: bootstrap system user before system project (FK constraint) The system project needs a valid ownerId that references an existing user. Create a system@mcpctl.local user via upsert before creating the project. Co-Authored-By: Claude Opus 4.6 --- src/mcpd/src/bootstrap/system-project.ts | 20 ++++++++++++--- .../tests/bootstrap-system-project.test.ts | 25 +++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) 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: {},