export interface KbxProblemBase { type: string title: string detail?: string | null correlationId?: string | null } export interface KbxValidationError { field?: string | null rowKey?: string | null code: string message: string } export interface KbxValidationProblem extends KbxProblemBase { type: 'validation' errors: KbxValidationError[] } export interface KbxProblemAction { id: string; label: string } export interface KbxBusinessProblem extends KbxProblemBase { type: 'business-rule' code: string actions?: KbxProblemAction[] } export interface KbxConflictProblem extends KbxProblemBase { type: 'conflict' code: string currentVersion?: number | null } export interface KbxPermissionProblem extends KbxProblemBase { type: 'permission' code: string } export interface KbxNotFoundProblem extends KbxProblemBase { type: 'not-found' code: string } export interface KbxIntegrationProblem extends KbxProblemBase { type: 'integration' code: string retryable: boolean } export interface KbxSystemProblem extends KbxProblemBase { type: 'system' code: string correlationId: string retryable?: boolean } export type KbxProblem = | KbxValidationProblem | KbxBusinessProblem | KbxConflictProblem | KbxPermissionProblem | KbxNotFoundProblem | KbxIntegrationProblem | KbxSystemProblem const problemTypes = new Set([ 'validation', 'business-rule', 'conflict', 'permission', 'not-found', 'integration', 'system', ]) export function isKbxProblem(value: unknown): value is KbxProblem { if (!value || typeof value !== 'object') return false const problem = value as Record return typeof problem.type === 'string' && problemTypes.has(problem.type as KbxProblem['type']) && typeof problem.title === 'string' } export function kbxUnexpectedProblem(correlationId: string, detail?: string, retryable = false): KbxSystemProblem { return { type: 'system', code: 'UNEXPECTED_ERROR', title: '요청을 처리하지 못했습니다.', detail, correlationId, retryable, } } export function isRetryableKbxProblem(value: unknown): boolean { return isKbxProblem(value) && ((value.type === 'system' && value.retryable === true) || (value.type === 'integration' && value.retryable === true)) }