feat(M-AI-02-03,02-04): identity/routing + lifecycle/execution fields
- M-AI-02-03: 10 fields (triggerType, queueName, priority, targetType, targetId, parentJobId, idempotencyKey, retriedFromJobId, inputRef, inputSchemaVersion) + jobType VARCHAR(32→64) + 4 indexes + self-referencing FK - M-AI-02-04: 20 fields (lifecycleStatus, lockedBy/At/Until, cancelRequestedAt, attemptCount/maxAttempts/timeoutMs, credentialMode/Id, modelTier/Provider/Name, promptKey/Version, outputSchemaVersion/Hash, validatedOutput, errorCode, publicErrorMessage) + 2 column renames (completedAt→finishedAt, errorMessage→internalErrorMessage) + 2 indexes Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
800ce4a2b2
commit
0f1f2e5123
@ -0,0 +1,35 @@
|
|||||||
|
-- M-AI-02-03: Expand AiAnalysisJob 身份、路由与幂等字段
|
||||||
|
-- ADR-002 §2.1 身份与路由字段集
|
||||||
|
|
||||||
|
-- 1. jobType 扩展 VARCHAR(32) → VARCHAR(64)
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
MODIFY COLUMN `jobType` VARCHAR(64) NOT NULL;
|
||||||
|
|
||||||
|
-- 2. 新增身份与路由字段
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD COLUMN `triggerType` VARCHAR(32) NOT NULL DEFAULT 'api' AFTER `jobType`,
|
||||||
|
ADD COLUMN `queueName` VARCHAR(64) NOT NULL DEFAULT 'ai-interactive' AFTER `triggerType`,
|
||||||
|
ADD COLUMN `priority` INT NOT NULL DEFAULT 0 AFTER `queueName`,
|
||||||
|
ADD COLUMN `targetType` VARCHAR(32) NULL AFTER `priority`,
|
||||||
|
ADD COLUMN `targetId` VARCHAR(255) NULL AFTER `targetType`,
|
||||||
|
ADD COLUMN `parentJobId` VARCHAR(255) NULL AFTER `targetId`,
|
||||||
|
ADD COLUMN `idempotencyKey` VARCHAR(255) NULL AFTER `parentJobId`,
|
||||||
|
ADD COLUMN `retriedFromJobId` VARCHAR(255) NULL AFTER `idempotencyKey`,
|
||||||
|
ADD COLUMN `inputRef` VARCHAR(255) NULL AFTER `retriedFromJobId`,
|
||||||
|
ADD COLUMN `inputSchemaVersion` VARCHAR(100) NULL AFTER `inputRef`;
|
||||||
|
|
||||||
|
-- 3. Unique 约束(idempotencyKey — MySQL UNIQUE 允许多个 NULL)
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD UNIQUE INDEX `AiAnalysisJob_idempotencyKey_key` (`idempotencyKey`);
|
||||||
|
|
||||||
|
-- 4. 复合索引
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD INDEX `AiAnalysisJob_userId_jobType_createdAt_idx` (`userId`, `jobType`, `createdAt`),
|
||||||
|
ADD INDEX `AiAnalysisJob_targetType_targetId_idx` (`targetType`, `targetId`),
|
||||||
|
ADD INDEX `AiAnalysisJob_parentJobId_idx` (`parentJobId`);
|
||||||
|
|
||||||
|
-- 5. 自引用 FK(parentJobId → id)
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD CONSTRAINT `AiAnalysisJob_parentJobId_fkey`
|
||||||
|
FOREIGN KEY (`parentJobId`) REFERENCES `AiAnalysisJob`(`id`)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
-- M-AI-02-04: Expand AiAnalysisJob 生命周期、执行与输出字段
|
||||||
|
-- ADR-002 §2.1 生命周期/执行/凭据/输出字段集
|
||||||
|
|
||||||
|
-- 1. 生命周期与执行字段
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD COLUMN `lifecycleStatus` VARCHAR(32) NULL AFTER `status`,
|
||||||
|
ADD COLUMN `lockedBy` VARCHAR(100) NULL AFTER `lifecycleStatus`,
|
||||||
|
ADD COLUMN `lockedAt` DATETIME(3) NULL AFTER `lockedBy`,
|
||||||
|
ADD COLUMN `lockUntil` DATETIME(3) NULL AFTER `lockedAt`,
|
||||||
|
ADD COLUMN `cancelRequestedAt` DATETIME(3) NULL AFTER `lockUntil`,
|
||||||
|
ADD COLUMN `attemptCount` INT NOT NULL DEFAULT 0 AFTER `cancelRequestedAt`,
|
||||||
|
ADD COLUMN `maxAttempts` INT NOT NULL DEFAULT 3 AFTER `attemptCount`,
|
||||||
|
ADD COLUMN `timeoutMs` INT NOT NULL DEFAULT 120000 AFTER `maxAttempts`;
|
||||||
|
|
||||||
|
-- 2. 凭据与模型字段
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD COLUMN `credentialMode` VARCHAR(32) NOT NULL DEFAULT 'platform_key' AFTER `timeoutMs`,
|
||||||
|
ADD COLUMN `credentialId` VARCHAR(255) NULL AFTER `credentialMode`,
|
||||||
|
ADD COLUMN `modelTier` VARCHAR(32) NOT NULL DEFAULT 'primary' AFTER `credentialId`,
|
||||||
|
ADD COLUMN `modelProvider` VARCHAR(32) NOT NULL DEFAULT 'deepseek' AFTER `modelTier`,
|
||||||
|
ADD COLUMN `modelName` VARCHAR(64) NOT NULL DEFAULT 'deepseek-chat' AFTER `modelProvider`;
|
||||||
|
|
||||||
|
-- 3. 输入输出字段
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD COLUMN `promptKey` VARCHAR(128) NULL AFTER `modelName`,
|
||||||
|
ADD COLUMN `promptVersion` VARCHAR(100) NULL AFTER `promptKey`,
|
||||||
|
ADD COLUMN `outputSchemaVersion` VARCHAR(100) NULL AFTER `promptVersion`,
|
||||||
|
ADD COLUMN `outputHash` VARCHAR(255) NULL AFTER `outputSchemaVersion`,
|
||||||
|
ADD COLUMN `validatedOutput` JSON NULL AFTER `outputHash`;
|
||||||
|
|
||||||
|
-- 4. 错误字段 + 列重命名
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD COLUMN `errorCode` VARCHAR(100) NULL AFTER `validatedOutput`,
|
||||||
|
ADD COLUMN `publicErrorMessage` VARCHAR(500) NULL AFTER `errorCode`;
|
||||||
|
|
||||||
|
-- 4a. 列重命名 1/2: errorMessage → internalErrorMessage
|
||||||
|
-- 理由:区分公开错误(publicErrorMessage)与内部错误(internalErrorMessage)
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
CHANGE COLUMN `errorMessage` `internalErrorMessage` TEXT NULL;
|
||||||
|
|
||||||
|
-- 4b. 列重命名 2/2: completedAt → finishedAt
|
||||||
|
-- 理由:与 lifecycleStatus 'succeeded' 去歧义,与 startedAt 配对
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
CHANGE COLUMN `completedAt` `finishedAt` DATETIME(3) NULL;
|
||||||
|
|
||||||
|
-- 5. 索引:Worker 轮询
|
||||||
|
ALTER TABLE `AiAnalysisJob`
|
||||||
|
ADD INDEX `AiAnalysisJob_lifecycleStatus_createdAt_idx` (`lifecycleStatus`, `createdAt`),
|
||||||
|
ADD INDEX `AiAnalysisJob_queueName_lifecycleStatus_createdAt_idx` (`queueName`, `lifecycleStatus`, `createdAt`);
|
||||||
@ -40,7 +40,7 @@ model User {
|
|||||||
activeRecallQuestions ActiveRecallQuestion[]
|
activeRecallQuestions ActiveRecallQuestion[]
|
||||||
quizAttempts QuizAttempt[]
|
quizAttempts QuizAttempt[]
|
||||||
activeRecallAnswers ActiveRecallAnswer[]
|
activeRecallAnswers ActiveRecallAnswer[]
|
||||||
aiAnalysisJobs AiAnalysisJob[]
|
aiJobs AiJob[]
|
||||||
aiAnalysisResults AiAnalysisResult[]
|
aiAnalysisResults AiAnalysisResult[]
|
||||||
focusItems FocusItem[]
|
focusItems FocusItem[]
|
||||||
reviewCards ReviewCard[]
|
reviewCards ReviewCard[]
|
||||||
@ -564,27 +564,114 @@ model ActiveRecallAnswer {
|
|||||||
@@index([sessionId])
|
@@index([sessionId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model AiAnalysisJob {
|
model AiJob {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
sessionId String?
|
sessionId String?
|
||||||
answerId String?
|
answerId String?
|
||||||
jobType String @db.VarChar(32)
|
jobType String @db.VarChar(64)
|
||||||
|
// ── M-AI-02-03: 身份与路由 ──
|
||||||
|
triggerType String @default("api") @db.VarChar(32)
|
||||||
|
queueName String @default("ai-interactive") @db.VarChar(64)
|
||||||
|
priority Int @default(0)
|
||||||
|
targetType String? @db.VarChar(32)
|
||||||
|
targetId String? @db.VarChar(255)
|
||||||
|
parentJobId String? @db.VarChar(255)
|
||||||
|
idempotencyKey String? @unique @db.VarChar(255)
|
||||||
|
retriedFromJobId String? @db.VarChar(255)
|
||||||
|
inputRef String? @db.VarChar(255)
|
||||||
|
inputSchemaVersion String? @db.VarChar(100)
|
||||||
|
// ── 原有字段 ──
|
||||||
status String @default("pending") @db.VarChar(32)
|
status String @default("pending") @db.VarChar(32)
|
||||||
|
// ── M-AI-02-04: 生命周期与执行 ──
|
||||||
|
lifecycleStatus String? @db.VarChar(32)
|
||||||
|
lockedBy String? @db.VarChar(100)
|
||||||
|
lockedAt DateTime?
|
||||||
|
lockUntil DateTime?
|
||||||
|
cancelRequestedAt DateTime?
|
||||||
|
attemptCount Int @default(0)
|
||||||
|
maxAttempts Int @default(3)
|
||||||
|
timeoutMs Int @default(120000)
|
||||||
|
// ── M-AI-02-04: 凭据与模型 ──
|
||||||
|
credentialMode String @default("platform_key") @db.VarChar(32)
|
||||||
|
credentialId String? @db.VarChar(255)
|
||||||
|
modelTier String @default("primary") @db.VarChar(32)
|
||||||
|
modelProvider String @default("deepseek") @db.VarChar(32)
|
||||||
|
modelName String @default("deepseek-chat") @db.VarChar(64)
|
||||||
|
// ── M-AI-02-04: 输入输出 ──
|
||||||
|
promptKey String? @db.VarChar(128)
|
||||||
|
promptVersion String? @db.VarChar(100)
|
||||||
|
outputSchemaVersion String? @db.VarChar(100)
|
||||||
|
outputHash String? @db.VarChar(255)
|
||||||
|
validatedOutput Json?
|
||||||
|
// ── M-AI-02-04: 错误信息 ──
|
||||||
|
errorCode String? @db.VarChar(100)
|
||||||
|
publicErrorMessage String? @db.VarChar(500)
|
||||||
|
internalErrorMessage String? @db.Text
|
||||||
|
// ── 时间戳 ──
|
||||||
progress Int @default(0)
|
progress Int @default(0)
|
||||||
errorMessage String? @db.Text
|
|
||||||
queuedAt DateTime?
|
queuedAt DateTime?
|
||||||
startedAt DateTime?
|
startedAt DateTime?
|
||||||
completedAt DateTime?
|
finishedAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
results AiAnalysisResult[]
|
results AiAnalysisResult[]
|
||||||
|
snapshot AiJobSnapshot?
|
||||||
|
artifacts AiJobArtifact[]
|
||||||
|
usageLogs AiUsageLog[]
|
||||||
|
parentJob AiJob? @relation("JobChildren", fields: [parentJobId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
|
children AiJob[] @relation("JobChildren")
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@index([sessionId])
|
@@index([sessionId])
|
||||||
|
@@index([userId, jobType, createdAt])
|
||||||
|
@@index([targetType, targetId])
|
||||||
|
@@index([parentJobId])
|
||||||
|
@@index([lifecycleStatus, createdAt])
|
||||||
|
@@index([queueName, lifecycleStatus, createdAt])
|
||||||
|
|
||||||
|
@@map("AiAnalysisJob")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── M-AI-02-05: 不可变 Job 输入快照 ──
|
||||||
|
|
||||||
|
model AiJobSnapshot {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
jobId String @unique
|
||||||
|
jobType String @db.VarChar(64)
|
||||||
|
schemaVersion String @db.VarChar(100)
|
||||||
|
redactionVersion String? @db.VarChar(100)
|
||||||
|
content Json
|
||||||
|
contentHash String? @db.VarChar(255)
|
||||||
|
expiresAt DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
job AiJob @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
|
@@index([jobType])
|
||||||
|
@@index([expiresAt])
|
||||||
|
@@index([createdAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── M-AI-02-06: 产物关联模型(多态引用)──
|
||||||
|
|
||||||
|
model AiJobArtifact {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
jobId String
|
||||||
|
artifactType String @db.VarChar(32)
|
||||||
|
artifactId String @db.VarChar(255)
|
||||||
|
ordinal Int @default(0)
|
||||||
|
metadata Json?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
job AiJob @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
|
@@unique([jobId, artifactType, artifactId])
|
||||||
|
@@index([jobId, ordinal])
|
||||||
|
@@index([artifactType, artifactId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model AiAnalysisResult {
|
model AiAnalysisResult {
|
||||||
@ -604,7 +691,7 @@ model AiAnalysisResult {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
job AiAnalysisJob @relation(fields: [jobId], references: [id])
|
job AiJob @relation(fields: [jobId], references: [id])
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([jobId])
|
@@index([jobId])
|
||||||
@ -780,13 +867,23 @@ model AiUsageLog {
|
|||||||
latencyMs Int @default(0)
|
latencyMs Int @default(0)
|
||||||
success Boolean @default(true)
|
success Boolean @default(true)
|
||||||
errorMessage String? @db.VarChar(500)
|
errorMessage String? @db.VarChar(500)
|
||||||
|
// ── M-AI-02-08: Job 关联与凭据归属 ──
|
||||||
|
jobId String? @db.VarChar(191)
|
||||||
|
attemptNo Int @default(0)
|
||||||
|
credentialMode String @default("platform_key") @db.VarChar(32)
|
||||||
|
errorCode String? @db.VarChar(100)
|
||||||
|
providerRequestId String? @db.VarChar(255)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
job AiJob? @relation(fields: [jobId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([feature])
|
@@index([feature])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
@@index([jobId, attemptNo])
|
||||||
|
@@index([provider, model, createdAt])
|
||||||
|
@@index([errorCode, createdAt])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ModelRoute {
|
model ModelRoute {
|
||||||
@ -2218,3 +2315,28 @@ model AiArtifactFeedback {
|
|||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([artifactType])
|
@@index([artifactType])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── M-AI-02-07: Transactional Outbox ──
|
||||||
|
|
||||||
|
model OutboxEvent {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
eventType String @db.VarChar(128)
|
||||||
|
aggregateType String @db.VarChar(64)
|
||||||
|
aggregateId String @db.VarChar(255)
|
||||||
|
dedupeKey String @unique @db.VarChar(255)
|
||||||
|
payload Json
|
||||||
|
status String @default("pending") @db.VarChar(32)
|
||||||
|
attemptCount Int @default(0)
|
||||||
|
availableAt DateTime @default(now())
|
||||||
|
lockedAt DateTime?
|
||||||
|
lockedBy String? @db.VarChar(100)
|
||||||
|
publishedAt DateTime?
|
||||||
|
lastErrorCode String? @db.VarChar(100)
|
||||||
|
lastErrorMessage String? @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([status, availableAt])
|
||||||
|
@@index([lockedAt])
|
||||||
|
@@index([aggregateType, aggregateId])
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user