feat(ai-runtime): complete ai runtime policy refactor (ADR-035)
CI / CD Pipeline / build (push) Successful in 4m16s
CI / CD Pipeline / deploy (push) Successful in 11m51s

This commit is contained in:
2026-06-12 08:07:15 +07:00
parent 71c5e88181
commit 0227b7b982
63 changed files with 3566 additions and 451 deletions
+74 -34
View File
@@ -1,53 +1,93 @@
// File: src/modules/ai/dto/create-ai-job.dto.ts
// Change Log
// - 2026-05-15: เพิ่ม DTO สำหรับ enqueue AI jobs ตาม ADR-023A US1.
// File: backend/src/modules/ai/dto/create-ai-job.dto.ts
// Change Log:
// - 2026-06-11: Refactored CreateAiJobDto to support new AI runtime policy contract (Option B)
// - 2026-06-11: เพิ่ม IsObject ใน class-validator import
// - 2026-06-11: ใช้ import type สำหรับ PublicJobType เพื่อแก้ปัญหา TS1272
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsIn,
IsNotEmpty,
IsObject,
IsEnum,
IsOptional,
IsString,
IsUUID,
IsObject,
registerDecorator,
ValidationOptions,
ValidationArguments,
} from 'class-validator';
import type { PublicJobType } from '../interfaces/execution-policy.interface';
export const AI_JOB_TYPES = [
'ai-suggest',
'rag-query',
'ocr',
'extract-metadata',
'embed-document',
] as const;
/**
* Custom decorator to forbid specific properties in payload.
* เดคอเรเตอร์สำหรับป้องกันไม่ให้ส่งฟิลด์ที่กำหนดมาใน API payload
*/
export function IsForbidden(validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
name: 'isForbidden',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: unknown) {
return value === undefined;
},
defaultMessage(args: ValidationArguments) {
return `${args.property} is forbidden in payload. Backend determines execution policy.`;
},
},
});
};
}
export type CreateAiJobType = (typeof AI_JOB_TYPES)[number];
/** DTO สำหรับส่งงาน AI เข้า BullMQ โดยใช้ publicId เท่านั้นตาม ADR-019 */
export class CreateAiJobDto {
@ApiProperty({ description: 'Attachment/document publicId สำหรับงาน AI' })
@IsUUID()
documentPublicId!: string;
@ApiProperty({ description: 'Project publicId สำหรับ project isolation' })
@IsUUID()
projectPublicId!: string;
@ApiProperty({
enum: AI_JOB_TYPES,
enum: ['auto-fill-document', 'migrate-document', 'rag-query'],
description: 'ชนิดงาน AI ที่ต้อง enqueue',
})
@IsIn(AI_JOB_TYPES)
jobType!: CreateAiJobType;
@ApiProperty({ description: 'Idempotency key จาก request header/body' })
@IsString()
@IsNotEmpty()
idempotencyKey!: string;
@IsEnum(['auto-fill-document', 'migrate-document', 'rag-query'])
type!: PublicJobType;
@ApiPropertyOptional({
description: 'Payload เพิ่มเติม เช่น pdfPath, extractedText, question',
description: 'Document publicId (UUIDv7) สำหรับงาน AI',
})
@IsOptional()
@IsUUID('all')
documentPublicId?: string;
@ApiPropertyOptional({
description: 'Attachment publicId (UUIDv7) สำหรับงาน AI',
})
@IsOptional()
@IsUUID('all')
attachmentPublicId?: string;
@ApiPropertyOptional({
description: 'Payload ข้อมูลเพิ่มเติมสำหรับงานแต่ละประเภท',
})
@IsOptional()
@IsObject()
payload?: Record<string, unknown>;
@ApiPropertyOptional({
description: 'Project publicId สำหรับ project isolation',
})
@IsOptional()
@IsUUID('all')
projectPublicId?: string;
// ฟิลด์ต้องห้ามตามข้อกำหนด FR-A01 เพื่อป้องกันการแทรกแซง policy จาก caller
@IsForbidden()
executionProfile?: unknown;
@IsForbidden()
model?: unknown;
@IsForbidden()
temperature?: unknown;
@IsForbidden()
top_p?: unknown;
@IsForbidden()
maxTokens?: unknown;
}