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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<void> {
|
||||
// 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<void
|
||||
prompt: '',
|
||||
proxyMode: 'direct',
|
||||
gated: false,
|
||||
ownerId: SYSTEM_OWNER_ID,
|
||||
ownerId: systemUser.id,
|
||||
},
|
||||
update: {}, // Don't overwrite user edits to the project itself
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { bootstrapSystemProject, SYSTEM_PROJECT_NAME, SYSTEM_OWNER_ID, getSystemPromptNames } from '../src/bootstrap/system-project.js';
|
||||
import { bootstrapSystemProject, SYSTEM_PROJECT_NAME, getSystemPromptNames } from '../src/bootstrap/system-project.js';
|
||||
import type { PrismaClient } from '@prisma/client';
|
||||
|
||||
function mockPrisma(): PrismaClient {
|
||||
@@ -7,6 +7,13 @@ function mockPrisma(): PrismaClient {
|
||||
let promptIdCounter = 1;
|
||||
|
||||
return {
|
||||
user: {
|
||||
upsert: vi.fn(async () => ({
|
||||
id: 'sys-user-id',
|
||||
email: 'system@mcpctl.local',
|
||||
name: 'System',
|
||||
})),
|
||||
},
|
||||
project: {
|
||||
upsert: vi.fn(async (args: { where: { name: string }; create: Record<string, unknown>; update: Record<string, unknown> }) => ({
|
||||
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: {},
|
||||
|
||||
Reference in New Issue
Block a user