drop FK on owner_token to support master-key callers

The proxy's master key is not stored in LiteLLM_VerificationToken, so a
hard FK from LiteLLM_ScheduledTaskTable.owner_token blocked every
master-key task creation with RecordNotFoundError on the nested connect.

Decision: keep owner_token as a plain string column. Cleanup of orphaned
rows when a key is deleted is now an operator concern (acceptable —
rows are small, deletions of keys are infrequent, and the alternative
locks out the most common local-dev auth path).

This change:
- removes the @relation declaration from all three schema.prisma files
- removes the AddForeignKey statement from the migration
- switches create_task back to the scalar owner_token write (no nested
  connect required)
- documents the rationale inline on the model

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krrish Dholakia 2026-04-29 15:56:03 -07:00
parent 93f8bec80c
commit e871514c1b
5 changed files with 16 additions and 16 deletions

View file

@ -40,13 +40,6 @@ CREATE INDEX "LiteLLM_ScheduledTaskTable_team_id_idx"
CREATE INDEX "LiteLLM_ScheduledTaskTable_agent_id_status_idx"
ON "LiteLLM_ScheduledTaskTable" ("agent_id", "status");
-- AddForeignKey
ALTER TABLE "LiteLLM_ScheduledTaskTable"
ADD CONSTRAINT "LiteLLM_ScheduledTaskTable_owner_token_fkey"
FOREIGN KEY ("owner_token")
REFERENCES "LiteLLM_VerificationToken" ("token")
ON DELETE CASCADE ON UPDATE CASCADE;
-- Hand-appended (Prisma cannot express partial indexes or CHECK constraints):
-- Partial index over rows the ticker actually scans.

View file

@ -410,7 +410,6 @@ model LiteLLM_VerificationToken {
litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id])
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
jwt_key_mappings LiteLLM_JWTKeyMapping[]
scheduled_tasks LiteLLM_ScheduledTaskTable[]
// SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub"
// SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2
@ -1295,8 +1294,12 @@ model LiteLLM_AdaptiveRouterSession {
model LiteLLM_ScheduledTaskTable {
task_id String @id @default(uuid())
// owner_token holds the hashed verification token of the calling key.
// Intentionally not declared as a foreign key — the proxy's master key
// has no row in LiteLLM_VerificationToken, so a hard FK would block any
// master-key caller from creating a task. Cleanup of orphaned rows is
// an operator responsibility.
owner_token String
owner_key LiteLLM_VerificationToken @relation(fields: [owner_token], references: [token], onDelete: Cascade)
user_id String?
team_id String?
agent_id String?

View file

@ -77,9 +77,7 @@ async def create_task(
) -> Any:
return await prisma_client.db.litellm_scheduledtasktable.create(
data={
# Prisma exposes the FK via the relation field (`owner_key`),
# not the scalar column. Use connect to satisfy the input type.
"owner_key": {"connect": {"token": owner_token}},
"owner_token": owner_token,
"user_id": user_id,
"team_id": team_id,
"agent_id": agent_id,

View file

@ -410,7 +410,6 @@ model LiteLLM_VerificationToken {
litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id])
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
jwt_key_mappings LiteLLM_JWTKeyMapping[]
scheduled_tasks LiteLLM_ScheduledTaskTable[]
// SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub"
// SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2
@ -1295,8 +1294,12 @@ model LiteLLM_AdaptiveRouterSession {
model LiteLLM_ScheduledTaskTable {
task_id String @id @default(uuid())
// owner_token holds the hashed verification token of the calling key.
// Intentionally not declared as a foreign key — the proxy's master key
// has no row in LiteLLM_VerificationToken, so a hard FK would block any
// master-key caller from creating a task. Cleanup of orphaned rows is
// an operator responsibility.
owner_token String
owner_key LiteLLM_VerificationToken @relation(fields: [owner_token], references: [token], onDelete: Cascade)
user_id String?
team_id String?
agent_id String?

View file

@ -410,7 +410,6 @@ model LiteLLM_VerificationToken {
litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id])
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
jwt_key_mappings LiteLLM_JWTKeyMapping[]
scheduled_tasks LiteLLM_ScheduledTaskTable[]
// SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub"
// SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2
@ -1295,8 +1294,12 @@ model LiteLLM_AdaptiveRouterSession {
model LiteLLM_ScheduledTaskTable {
task_id String @id @default(uuid())
// owner_token holds the hashed verification token of the calling key.
// Intentionally not declared as a foreign key — the proxy's master key
// has no row in LiteLLM_VerificationToken, so a hard FK would block any
// master-key caller from creating a task. Cleanup of orphaned rows is
// an operator responsibility.
owner_token String
owner_key LiteLLM_VerificationToken @relation(fields: [owner_token], references: [token], onDelete: Cascade)
user_id String?
team_id String?
agent_id String?