Skip to content

Commit 6845175

Browse files
GabrielDraporclaude
andcommitted
fix(core): index ai_messages.execution_id and give ai_sessions.execution_id a foreign key
ai_messages.execution_id had an ON DELETE SET NULL foreign key but no index, so deleting an execution (cascaded from a finding delete) scanned the table to null it — and the agent-trace viewer reads a finding's turns by execution. ai_sessions.execution_id had neither, so it could dangle after an execution delete. Both now carry the FK (SET NULL / NO ACTION, matching the execution table's own relations) and an index. The two session↔execution relations are named so Prisma can tell the execution that opened a session apart from the session an execution records once it completes. The speculative ix_ai_message_workspace_turnkind_time is dropped: metering still groups by `kind`, and nothing in the stack queries by turnKind yet. The unmerged migration is edited in place. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
1 parent 1ca2f17 commit 6845175

2 files changed

Lines changed: 26 additions & 12 deletions

File tree

frontend/packages/core/prisma/migrations/20260901000001_ai_message_attribution/migration.sql

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@ ALTER TABLE "ai_messages"
44
ADD COLUMN "execution_id" VARCHAR,
55
ADD COLUMN "initiator_user_id" VARCHAR;
66
ALTER TABLE "ai_messages" ADD CONSTRAINT "ai_messages_execution_id_fkey"
7-
FOREIGN KEY ("execution_id") REFERENCES "detector_rca_executions"("id") ON DELETE SET NULL;
7+
FOREIGN KEY ("execution_id") REFERENCES "detector_rca_executions"("id") ON DELETE SET NULL ON UPDATE NO ACTION;
8+
-- Deleting an execution (cascade from a finding delete) nulls these FKs; without
9+
-- an index each delete scans the table. The agent-trace viewer also reads a
10+
-- finding's turns by execution.
11+
CREATE INDEX "ix_ai_message_execution_id" ON "ai_messages"("execution_id");
12+
813
ALTER TABLE "ai_sessions" ADD COLUMN "execution_id" VARCHAR;
14+
ALTER TABLE "ai_sessions" ADD CONSTRAINT "ai_sessions_execution_id_fkey"
15+
FOREIGN KEY ("execution_id") REFERENCES "detector_rca_executions"("id") ON DELETE SET NULL ON UPDATE NO ACTION;
16+
CREATE INDEX "ix_ai_session_execution_id" ON "ai_sessions"("execution_id");
917

1018
-- Backfill from the legacy kind. Historical system-session turns cannot be split into
1119
-- execution vs follow-up after the fact; all are attributed to the execution.
@@ -14,4 +22,3 @@ UPDATE "ai_messages" SET "turn_kind" = 'detector' WHERE "kind" = 'detector'
1422
UPDATE "ai_messages" SET "turn_kind" = 'digest' WHERE "kind" = 'digest-summary';
1523
UPDATE "ai_messages" m SET "initiator_user_id" = s."user_id"
1624
FROM "ai_sessions" s WHERE m."session_id" = s."id" AND s."user_id" IS NOT NULL;
17-
CREATE INDEX "ix_ai_message_workspace_turnkind_time" ON "ai_messages"("workspace_id", "turn_kind", "create_time");

frontend/packages/core/prisma/schema.prisma

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,15 @@ model AISession {
351351
updateTime DateTime @default(now()) @updatedAt @map("update_time") @db.Timestamp(6)
352352
messages AIMessage[]
353353
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction)
354-
rcaExecutions DetectorRcaExecution[]
354+
execution DetectorRcaExecution? @relation("sessionExecution", fields: [executionId], references: [id], onDelete: SetNull, onUpdate: NoAction)
355+
// Executions that recorded this session as theirs once they completed (see
356+
// DetectorRcaExecution.sessionId) — the reverse of `execution` above.
357+
rcaExecutions DetectorRcaExecution[] @relation("executionSession")
355358
356359
@@index([projectId], map: "ix_ai_session_project_id")
357360
@@index([workspaceId], map: "ix_ai_session_workspace_id")
358361
@@index([userId], map: "ix_ai_session_user_id")
362+
@@index([executionId], map: "ix_ai_session_execution_id")
359363
@@map("ai_sessions")
360364
}
361365

@@ -369,10 +373,10 @@ enum TurnKind {
369373

370374
// AI Messages - Individual messages within an AI session
371375
model AIMessage {
372-
id String @id @default(cuid()) @db.VarChar
373-
workspaceId String @map("workspace_id") @db.VarChar // direct workspace pointer for per-kind aggregation (detector scans have no session)
374-
sessionId String? @map("session_id") @db.VarChar // nullable: detector scans have no chat session
375-
kind String @default("chat") @db.VarChar // "chat" | "rca" | "detector" | "digest-summary" — categorical tag; usage metering aggregates only chat|rca|detector (digest-summary is recorded, not metered)
376+
id String @id @default(cuid()) @db.VarChar
377+
workspaceId String @map("workspace_id") @db.VarChar // direct workspace pointer for per-kind aggregation (detector scans have no session)
378+
sessionId String? @map("session_id") @db.VarChar // nullable: detector scans have no chat session
379+
kind String @default("chat") @db.VarChar // "chat" | "rca" | "detector" | "digest-summary" — categorical tag; usage metering aggregates only chat|rca|detector (digest-summary is recorded, not metered)
376380
// Per-turn attribution. `kind` is kept one release for old readers and is
377381
// derived from turnKind at write time; metering will move to turnKind (E-09 follow-up).
378382
turnKind TurnKind @default(chat) @map("turn_kind")
@@ -390,11 +394,11 @@ model AIMessage {
390394
createTime DateTime @default(now()) @map("create_time") @db.Timestamp(6)
391395
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade, onUpdate: NoAction)
392396
session AISession? @relation(fields: [sessionId], references: [id], onDelete: Cascade, onUpdate: NoAction)
393-
execution DetectorRcaExecution? @relation(fields: [executionId], references: [id], onDelete: SetNull)
397+
execution DetectorRcaExecution? @relation(fields: [executionId], references: [id], onDelete: SetNull, onUpdate: NoAction)
394398
395399
@@index([sessionId], map: "ix_ai_message_session_id")
396400
@@index([workspaceId, kind, createTime], map: "ix_ai_message_workspace_kind_time")
397-
@@index([workspaceId, turnKind, createTime], map: "ix_ai_message_workspace_turnkind_time")
401+
@@index([executionId], map: "ix_ai_message_execution_id")
398402
@@map("ai_messages")
399403
}
400404

@@ -528,9 +532,12 @@ model DetectorRcaExecution {
528532
startedAt DateTime @default(now()) @map("started_at") @db.Timestamp(6)
529533
finishedAt DateTime? @map("finished_at") @db.Timestamp(6)
530534
531-
rca DetectorRca @relation(fields: [findingId], references: [findingId], onDelete: Cascade, onUpdate: NoAction)
532-
session AISession? @relation(fields: [sessionId], references: [id], onDelete: SetNull, onUpdate: NoAction)
533-
messages AIMessage[]
535+
rca DetectorRca @relation(fields: [findingId], references: [findingId], onDelete: Cascade, onUpdate: NoAction)
536+
session AISession? @relation("executionSession", fields: [sessionId], references: [id], onDelete: SetNull, onUpdate: NoAction)
537+
// Sessions opened for this execution (AISession.executionId, set at creation)
538+
// and the turns attributed to it (AIMessage.executionId).
539+
openedSessions AISession[] @relation("sessionExecution")
540+
messages AIMessage[]
534541
535542
@@unique([findingId, attempt], map: "uq_rca_execution_finding_attempt")
536543
@@map("detector_rca_executions")

0 commit comments

Comments
 (0)