package platform import ( "context" "encoding/json" "errors" "fmt" "infinite-canvas/backend/internal/kernel" "strings" "time" "infinite-canvas/backend/internal/model" ) const runtimePolicySettingKey = "runtime_policy" const ( maxRuntimeUploadMB int64 = 999 maxRuntimeStorageGB int64 = 999 maxRuntimeDataMB int64 = 999_999 maxRuntimeCount int64 = 999_999_999 maxRuntimeRate = 999_999 maxRuntimeConcurrency = 999 maxRuntimeTimeoutMinutes = 9_999 defaultWorkerConcurrency = 8 defaultChannelConcurrency = 8 ) // 画布 Agent 单步的输出上限与执行时限。上限是"每次模型调用"的边界(思考 + 正文 + 工具参数), // 时限是"这一次调用最多等多久"的墙钟;两者一起决定单步最坏耗时。0 在两个口径下都是"该边界不生效": // 上限为 0 表示不限制输出(只由执行时限兜底),时限为 0 表示沿用文本任务超时。 const ( MinRuntimeAgentStepOutputTokens = 256 MaxRuntimeAgentStepOutputTokens = 131_072 DefaultRuntimeAgentStepOutputTokens = 16_384 MinRuntimeAgentStepTimeoutSeconds = 30 MaxRuntimeAgentStepTimeoutSeconds = 3_600 DefaultRuntimeAgentStepTimeout = 0 MinRuntimeAgentSessions = 1 MaxRuntimeAgentSessions = 64 DefaultRuntimeAgentSessions = 30 ) // agentStepMaxOutputTokensDefault 读取部署级覆盖并按同一套范围夹取;环境变量无法表达"不限制"(0), // 那个档位只在管理端配置里设置。 func agentStepMaxOutputTokensDefault() int { value := envInt("CANVAS_AGENT_STEP_MAX_OUTPUT_TOKENS", DefaultRuntimeAgentStepOutputTokens) return min(max(value, MinRuntimeAgentStepOutputTokens), MaxRuntimeAgentStepOutputTokens) } func agentStepTimeoutSecondsDefault() int { value := envInt("CANVAS_AGENT_STEP_TIMEOUT_SECONDS", DefaultRuntimeAgentStepTimeout) if value <= 0 { return DefaultRuntimeAgentStepTimeout } return min(max(value, MinRuntimeAgentStepTimeoutSeconds), MaxRuntimeAgentStepTimeoutSeconds) } type RuntimeResourcePolicy struct { ResourceUploadMB int64 `json:"resourceUploadMB"` GeneratedFileMB int64 `json:"generatedFileMB"` DailyUploadMB int64 `json:"dailyUploadMB"` StoredFileGB int64 `json:"storedFileGB"` StructuredDataMB int64 `json:"structuredDataMB"` TaskDataGB int64 `json:"taskDataGB"` AssetCount int64 `json:"assetCount"` CanvasCount int64 `json:"canvasCount"` TaskCount int64 `json:"taskCount"` APICallLogCount int64 `json:"apiCallLogCount"` RecycleBinRetentionDays int `json:"recycleBinRetentionDays"` } type RuntimeStoragePolicy struct { TransferTimeoutSeconds int `json:"transferTimeoutSeconds"` AccessURLTTLSeconds int `json:"accessURLTTLSeconds"` ProviderAccessURLTTLSeconds int `json:"providerAccessURLTTLSeconds"` NonSeekableBufferMB int64 `json:"nonSeekableBufferMB"` ErrorBodyKB int64 `json:"errorBodyKB"` } type RuntimeTaskPolicy struct { WorkerConcurrency int `json:"workerConcurrency"` ChannelConcurrency int `json:"channelConcurrency"` ActiveTaskLimit int `json:"activeTaskLimit"` ImageTimeoutMinutes int `json:"imageTimeoutMinutes"` TextTimeoutMinutes int `json:"textTimeoutMinutes"` AudioTimeoutMinutes int `json:"audioTimeoutMinutes"` VideoTimeoutMinutes int `json:"videoTimeoutMinutes"` StoryboardTimeoutMinutes int `json:"storyboardTimeoutMinutes"` DefaultTimeoutMinutes int `json:"defaultTimeoutMinutes"` // AgentStepMaxOutputTokens 是画布 Agent 单步模型调用的输出上限(思考 + 正文 + 工具调用参数)。 // 0 表示不限制输出,此时单步最坏耗时只由 AgentStepTimeoutSeconds 兜底。 AgentStepMaxOutputTokens int `json:"agentStepMaxOutputTokens"` // AgentStepTimeoutSeconds 是画布 Agent 单步模型调用的秒级墙钟;0 表示沿用文本任务超时。 AgentStepTimeoutSeconds int `json:"agentStepTimeoutSeconds"` // AgentMaxSessions 是同时进行中的画布 Agent 轮次上限。审批等待不占用名额。 AgentMaxSessions int `json:"agentMaxSessions"` } type RuntimeRequestPolicy struct { TaskCreatePerMinute int `json:"taskCreatePerMinute"` ResourceUploadPerMinute int `json:"resourceUploadPerMinute"` ResourceImportPerMinute int `json:"resourceImportPerMinute"` AssetWritePerMinute int `json:"assetWritePerMinute"` CanvasWritePerMinute int `json:"canvasWritePerMinute"` RegisterPerHour int `json:"registerPerHour"` EmailCodePerHour int `json:"emailCodePerHour"` LoginIPPerTenMinutes int `json:"loginIPPerTenMinutes"` LoginAccountPerTenMinutes int `json:"loginAccountPerTenMinutes"` SystemRelayPerMinute int `json:"systemRelayPerMinute"` CustomRelayPerMinute int `json:"customRelayPerMinute"` CustomRelayConcurrency int `json:"customRelayConcurrency"` CustomRelayRequestMB int64 `json:"customRelayRequestMB"` CustomRelayResponseMB int64 `json:"customRelayResponseMB"` CustomRelayTimeoutMinutes int `json:"customRelayTimeoutMinutes"` SystemRelayRequestMB int64 `json:"systemRelayRequestMB"` SystemRelayResponseMB int64 `json:"systemRelayResponseMB"` ChannelCircuitFailureCount int `json:"channelCircuitFailureCount"` ChannelCircuitOpenSeconds int `json:"channelCircuitOpenSeconds"` } type RuntimePolicySetting struct { Resource RuntimeResourcePolicy `json:"resource"` Storage RuntimeStoragePolicy `json:"storage"` Task RuntimeTaskPolicy `json:"task"` Request RuntimeRequestPolicy `json:"request"` } type PublicRuntimePolicySetting struct { RuntimePolicySetting Configured bool `json:"configured"` UpdatedBy string `json:"updatedBy"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } type PublicRuntimeLimits struct { ActiveTaskLimit int `json:"activeTaskLimit"` ResourceUploadMB int64 `json:"resourceUploadMB"` RecycleBinRetentionDays int `json:"recycleBinRetentionDays"` } // DefaultRuntimePolicy returns the effective baseline policy, including // deployment-level environment overrides. It is exported so composition-root // compatibility code and domain tests do not need to reach into platform's // private implementation. func DefaultRuntimePolicy() RuntimePolicySetting { return RuntimePolicySetting{ Resource: RuntimeResourcePolicy{ ResourceUploadMB: 50, GeneratedFileMB: 64, DailyUploadMB: 2048, StoredFileGB: 20, StructuredDataMB: 256, TaskDataGB: 1, AssetCount: 2_000, CanvasCount: 1_000, TaskCount: 20_000, APICallLogCount: 100_000, RecycleBinRetentionDays: 30, }, Storage: RuntimeStoragePolicy{ TransferTimeoutSeconds: 120, AccessURLTTLSeconds: 300, ProviderAccessURLTTLSeconds: 14_400, NonSeekableBufferMB: 64, ErrorBodyKB: 1, }, Task: RuntimeTaskPolicy{ WorkerConcurrency: defaultWorkerConcurrencyLimit(), ChannelConcurrency: defaultChannelConcurrencyLimit(), ActiveTaskLimit: 5, ImageTimeoutMinutes: 8, TextTimeoutMinutes: 8, AudioTimeoutMinutes: 8, VideoTimeoutMinutes: 60, StoryboardTimeoutMinutes: 20, DefaultTimeoutMinutes: 10, AgentStepMaxOutputTokens: agentStepMaxOutputTokensDefault(), AgentStepTimeoutSeconds: agentStepTimeoutSecondsDefault(), AgentMaxSessions: DefaultRuntimeAgentSessions, }, Request: RuntimeRequestPolicy{ TaskCreatePerMinute: 30, ResourceUploadPerMinute: 30, ResourceImportPerMinute: 30, AssetWritePerMinute: 120, CanvasWritePerMinute: 120, RegisterPerHour: 30, EmailCodePerHour: 60, LoginIPPerTenMinutes: 50, LoginAccountPerTenMinutes: 10, SystemRelayPerMinute: 120, CustomRelayPerMinute: 120, CustomRelayConcurrency: 4, CustomRelayRequestMB: 32, CustomRelayResponseMB: 32, CustomRelayTimeoutMinutes: 10, SystemRelayRequestMB: 64, SystemRelayResponseMB: 128, ChannelCircuitFailureCount: min(envInt("CANVAS_CHANNEL_CIRCUIT_FAILURES", 5), maxRuntimeConcurrency), ChannelCircuitOpenSeconds: min(envInt("CANVAS_CHANNEL_CIRCUIT_SECONDS", 60), 86_400), }, } } func selfUseRuntimePolicy() RuntimePolicySetting { value := DefaultRuntimePolicy() value.Resource = RuntimeResourcePolicy{ ResourceUploadMB: maxRuntimeUploadMB, GeneratedFileMB: maxRuntimeUploadMB, DailyUploadMB: maxRuntimeDataMB, StoredFileGB: maxRuntimeStorageGB, StructuredDataMB: maxRuntimeDataMB, TaskDataGB: maxRuntimeStorageGB, AssetCount: maxRuntimeCount, CanvasCount: maxRuntimeCount, TaskCount: maxRuntimeCount, APICallLogCount: maxRuntimeCount, RecycleBinRetentionDays: 0, } value.Task = RuntimeTaskPolicy{ WorkerConcurrency: maxRuntimeConcurrency, ChannelConcurrency: maxRuntimeConcurrency, ActiveTaskLimit: maxRuntimeConcurrency, ImageTimeoutMinutes: maxRuntimeTimeoutMinutes, TextTimeoutMinutes: maxRuntimeTimeoutMinutes, AudioTimeoutMinutes: maxRuntimeTimeoutMinutes, VideoTimeoutMinutes: maxRuntimeTimeoutMinutes, StoryboardTimeoutMinutes: maxRuntimeTimeoutMinutes, DefaultTimeoutMinutes: maxRuntimeTimeoutMinutes, AgentStepMaxOutputTokens: MaxRuntimeAgentStepOutputTokens, AgentStepTimeoutSeconds: MaxRuntimeAgentStepTimeoutSeconds, AgentMaxSessions: MaxRuntimeAgentSessions, } value.Request = RuntimeRequestPolicy{ TaskCreatePerMinute: maxRuntimeRate, ResourceUploadPerMinute: maxRuntimeRate, ResourceImportPerMinute: maxRuntimeRate, AssetWritePerMinute: maxRuntimeRate, CanvasWritePerMinute: maxRuntimeRate, RegisterPerHour: maxRuntimeRate, EmailCodePerHour: maxRuntimeRate, LoginIPPerTenMinutes: maxRuntimeRate, LoginAccountPerTenMinutes: maxRuntimeRate, SystemRelayPerMinute: maxRuntimeRate, CustomRelayPerMinute: maxRuntimeRate, CustomRelayConcurrency: maxRuntimeConcurrency, CustomRelayRequestMB: maxRuntimeUploadMB, CustomRelayResponseMB: maxRuntimeUploadMB, CustomRelayTimeoutMinutes: maxRuntimeTimeoutMinutes, SystemRelayRequestMB: maxRuntimeUploadMB, SystemRelayResponseMB: maxRuntimeUploadMB, ChannelCircuitFailureCount: maxRuntimeConcurrency, ChannelCircuitOpenSeconds: 1, } return value } func SelfUseRuntimePolicy() RuntimePolicySetting { return selfUseRuntimePolicy() } func (s *Service) RuntimePolicy() (RuntimePolicySetting, error) { if s.runtimePolicyCache == nil { _, value, err := s.readRuntimePolicy() return value, err } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() return s.runtimePolicyCache.Get(ctx, runtimePolicySettingKey, func(ctx context.Context) (RuntimePolicySetting, int, error) { reader := &Service{ repo: s.repo.WithContext(ctx), host: s.host, Coordinator: s.Coordinator, } _, value, err := reader.readRuntimePolicy() return value, 2048, err }) } func (s *Service) RuntimeConcurrencySetting() (RuntimeTaskPolicy, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() return s.concurrencyCache.Get(ctx, runtimePolicySettingKey, func(ctx context.Context) (RuntimeTaskPolicy, int, error) { reader := &Service{repo: s.repo.WithContext(ctx), host: s.host, Coordinator: s.Coordinator, concurrencyCache: s.concurrencyCache, runtimePolicyCache: s.runtimePolicyCache} policy, err := reader.RuntimePolicy() return policy.Task, 256, err }) } func (s *Service) PublicRuntimeLimits() (*PublicRuntimeLimits, error) { policy, err := s.RuntimePolicy() if err != nil { return nil, err } return &PublicRuntimeLimits{ ActiveTaskLimit: policy.Task.ActiveTaskLimit, ResourceUploadMB: policy.Resource.ResourceUploadMB, RecycleBinRetentionDays: policy.Resource.RecycleBinRetentionDays, }, nil } func (s *Service) AdminRuntimePolicySetting(actor *model.User) (*PublicRuntimePolicySetting, error) { if err := s.requireAdmin(actor); err != nil { return nil, err } setting, value, err := s.readRuntimePolicy() if err != nil { return nil, err } return publicRuntimePolicy(setting, value), nil } func (s *Service) AdminSelfUseRuntimePolicy(actor *model.User) (*PublicRuntimePolicySetting, error) { if err := s.requireAdmin(actor); err != nil { return nil, err } return publicRuntimePolicy(nil, selfUseRuntimePolicy()), nil } func (s *Service) UpdateRuntimePolicySetting(actor *model.User, value RuntimePolicySetting) (*PublicRuntimePolicySetting, error) { if err := s.requireAdmin(actor); err != nil { return nil, err } if err := validateRuntimePolicy(value); err != nil { return nil, err } current, before, err := s.readRuntimePolicy() if err != nil { return nil, err } encoded, err := json.Marshal(value) if err != nil { return nil, err } setting := model.SystemSetting{Key: runtimePolicySettingKey, ValueJSON: string(encoded), UpdatedBy: actor.ID} if current != nil { setting.CreatedAt = current.CreatedAt } if err := s.repo.SaveSystemSetting(&setting); err != nil { return nil, err } s.concurrencyCache.Clear() if s.runtimePolicyCache != nil { s.runtimePolicyCache.Clear() } if err := s.appendAdminAudit(actor, "runtime_policy.update", "system_setting", runtimePolicySettingKey, "更新资源与请求策略", map[string]any{"before": before, "after": value}); err != nil { return nil, err } return publicRuntimePolicy(&setting, value), nil } func (s *Service) ResetRuntimePolicySetting(actor *model.User) (*PublicRuntimePolicySetting, error) { if err := s.requireAdmin(actor); err != nil { return nil, err } _, before, err := s.readRuntimePolicy() if err != nil { return nil, err } if err := s.repo.DeleteSystemSetting(runtimePolicySettingKey); err != nil { return nil, err } s.concurrencyCache.Clear() if s.runtimePolicyCache != nil { s.runtimePolicyCache.Clear() } after := DefaultRuntimePolicy() if err := s.appendAdminAudit(actor, "runtime_policy.reset", "system_setting", runtimePolicySettingKey, "重置资源与请求策略", map[string]any{"before": before, "after": after}); err != nil { return nil, err } return publicRuntimePolicy(nil, after), nil } func (s *Service) readRuntimePolicy() (*model.SystemSetting, RuntimePolicySetting, error) { setting, err := s.repo.SystemSettingOptional(runtimePolicySettingKey) if err != nil { return nil, RuntimePolicySetting{}, err } if setting == nil { value := DefaultRuntimePolicy() return nil, value, validateRuntimePolicy(value) } value := DefaultRuntimePolicy() if strings.TrimSpace(setting.ValueJSON) == "" || json.Unmarshal([]byte(setting.ValueJSON), &value) != nil { return nil, RuntimePolicySetting{}, errors.New("资源与请求策略配置格式无效") } if err := validateRuntimePolicy(value); err != nil { return nil, RuntimePolicySetting{}, err } return setting, value, nil } func validateRuntimePolicy(value RuntimePolicySetting) error { resource := value.Resource for label, item := range map[string]int64{ "普通资源单文件": resource.ResourceUploadMB, "单个生成资源": resource.GeneratedFileMB, } { if item < 1 || item > maxRuntimeUploadMB { return kernel.BadAuthRequest(fmt.Sprintf("%s必须是 1-%d MB 的整数", label, maxRuntimeUploadMB)) } } if resource.DailyUploadMB < 1 || resource.DailyUploadMB > maxRuntimeDataMB { return kernel.BadAuthRequest(fmt.Sprintf("每日上传量必须是 1-%d MB 的整数", maxRuntimeDataMB)) } if resource.StoredFileGB < 1 || resource.StoredFileGB > maxRuntimeStorageGB || resource.TaskDataGB < 1 || resource.TaskDataGB > maxRuntimeStorageGB { return kernel.BadAuthRequest(fmt.Sprintf("账号文件与任务数据容量必须是 1-%d GB 的整数", maxRuntimeStorageGB)) } if resource.StructuredDataMB < 1 || resource.StructuredDataMB > maxRuntimeDataMB { return kernel.BadAuthRequest(fmt.Sprintf("结构化数据容量必须是 1-%d MB 的整数", maxRuntimeDataMB)) } storedMB := resource.StoredFileGB * 1024 if resource.ResourceUploadMB > storedMB || resource.GeneratedFileMB > storedMB { return kernel.BadAuthRequest("单文件上限不能大于账号文件总容量") } for label, item := range map[string]int64{ "素材数量": resource.AssetCount, "画布数量": resource.CanvasCount, "任务历史数量": resource.TaskCount, "请求日志数量": resource.APICallLogCount, } { if item < 1 || item > maxRuntimeCount { return kernel.BadAuthRequest(fmt.Sprintf("%s必须是 1-%d 的整数", label, maxRuntimeCount)) } } if resource.RecycleBinRetentionDays < 0 || resource.RecycleBinRetentionDays > 365 { return kernel.BadAuthRequest("回收站保留天数必须是 0-365 的整数 (0 表示不自动清理)") } storage := value.Storage if storage.TransferTimeoutSeconds < 10 || storage.TransferTimeoutSeconds > 3_600 { return kernel.BadAuthRequest("对象存储传输超时必须是 10-3600 秒的整数") } if storage.AccessURLTTLSeconds < 60 || storage.AccessURLTTLSeconds > 86_400 { return kernel.BadAuthRequest("对象存储访问地址有效期必须是 60-86400 秒的整数") } if storage.ProviderAccessURLTTLSeconds < 300 || storage.ProviderAccessURLTTLSeconds > 86_400 { return kernel.BadAuthRequest("模型输入资源地址有效期必须是 300-86400 秒的整数") } if storage.NonSeekableBufferMB < 1 || storage.NonSeekableBufferMB > maxRuntimeUploadMB { return kernel.BadAuthRequest(fmt.Sprintf("对象存储非可寻址上传缓冲必须是 1-%d MB 的整数", maxRuntimeUploadMB)) } if storage.ErrorBodyKB < 1 || storage.ErrorBodyKB > 1024 { return kernel.BadAuthRequest("对象存储错误响应保留必须是 1-1024 KB 的整数") } task := value.Task for label, item := range map[string]int{ "Worker 并发数": task.WorkerConcurrency, "全局渠道并发数": task.ChannelConcurrency, "活动任务上限": task.ActiveTaskLimit, } { if item < 1 || item > maxRuntimeConcurrency { return kernel.BadAuthRequest(fmt.Sprintf("%s必须是 1-%d 的整数", label, maxRuntimeConcurrency)) } } for label, item := range map[string]int{ "图片任务超时": task.ImageTimeoutMinutes, "文本任务超时": task.TextTimeoutMinutes, "音频任务超时": task.AudioTimeoutMinutes, "视频任务超时": task.VideoTimeoutMinutes, "分镜任务超时": task.StoryboardTimeoutMinutes, "默认任务超时": task.DefaultTimeoutMinutes, } { if item < 1 || item > maxRuntimeTimeoutMinutes { return kernel.BadAuthRequest(fmt.Sprintf("%s必须是 1-%d 分钟的整数", label, maxRuntimeTimeoutMinutes)) } } if task.AgentStepMaxOutputTokens != 0 && (task.AgentStepMaxOutputTokens < MinRuntimeAgentStepOutputTokens || task.AgentStepMaxOutputTokens > MaxRuntimeAgentStepOutputTokens) { return kernel.BadAuthRequest(fmt.Sprintf("Agent 单步输出上限必须是 0 或 %d-%d 的整数 (0 表示不限制)", MinRuntimeAgentStepOutputTokens, MaxRuntimeAgentStepOutputTokens)) } if task.AgentStepTimeoutSeconds != 0 && (task.AgentStepTimeoutSeconds < MinRuntimeAgentStepTimeoutSeconds || task.AgentStepTimeoutSeconds > MaxRuntimeAgentStepTimeoutSeconds) { return kernel.BadAuthRequest(fmt.Sprintf("Agent 单步超时必须是 0 或 %d-%d 秒的整数 (0 表示沿用文本任务超时)", MinRuntimeAgentStepTimeoutSeconds, MaxRuntimeAgentStepTimeoutSeconds)) } if task.AgentMaxSessions < MinRuntimeAgentSessions || task.AgentMaxSessions > MaxRuntimeAgentSessions { return kernel.BadAuthRequest(fmt.Sprintf("Agent 同时对话上限必须是 %d-%d 的整数", MinRuntimeAgentSessions, MaxRuntimeAgentSessions)) } request := value.Request for label, item := range map[string]int{ "任务创建频控": request.TaskCreatePerMinute, "资源上传频控": request.ResourceUploadPerMinute, "资源导入频控": request.ResourceImportPerMinute, "素材写入频控": request.AssetWritePerMinute, "画布写入频控": request.CanvasWritePerMinute, "注册频控": request.RegisterPerHour, "验证码频控": request.EmailCodePerHour, "登录 IP 频控": request.LoginIPPerTenMinutes, "登录账号频控": request.LoginAccountPerTenMinutes, "系统渠道频控": request.SystemRelayPerMinute, "自定义渠道频控": request.CustomRelayPerMinute, } { if item < 1 || item > maxRuntimeRate { return kernel.BadAuthRequest(fmt.Sprintf("%s必须是 1-%d 的整数", label, maxRuntimeRate)) } } if request.CustomRelayConcurrency < 1 || request.CustomRelayConcurrency > maxRuntimeConcurrency { return kernel.BadAuthRequest(fmt.Sprintf("自定义渠道并发必须是 1-%d 的整数", maxRuntimeConcurrency)) } for label, item := range map[string]int64{ "自定义渠道请求体": request.CustomRelayRequestMB, "自定义渠道响应体": request.CustomRelayResponseMB, "系统渠道请求体": request.SystemRelayRequestMB, "系统渠道响应体": request.SystemRelayResponseMB, } { if item < 1 || item > maxRuntimeUploadMB { return kernel.BadAuthRequest(fmt.Sprintf("%s必须是 1-%d MB 的整数", label, maxRuntimeUploadMB)) } } if request.CustomRelayTimeoutMinutes < 1 || request.CustomRelayTimeoutMinutes > maxRuntimeTimeoutMinutes { return kernel.BadAuthRequest(fmt.Sprintf("自定义渠道超时必须是 1-%d 分钟的整数", maxRuntimeTimeoutMinutes)) } if request.ChannelCircuitFailureCount < 1 || request.ChannelCircuitFailureCount > maxRuntimeConcurrency { return kernel.BadAuthRequest(fmt.Sprintf("渠道熔断失败次数必须是 1-%d 的整数", maxRuntimeConcurrency)) } if request.ChannelCircuitOpenSeconds < 1 || request.ChannelCircuitOpenSeconds > 86_400 { return kernel.BadAuthRequest("渠道熔断时长必须是 1-86400 秒的整数") } return nil } func ValidateRuntimePolicy(value RuntimePolicySetting) error { return validateRuntimePolicy(value) } func publicRuntimePolicy(setting *model.SystemSetting, value RuntimePolicySetting) *PublicRuntimePolicySetting { result := &PublicRuntimePolicySetting{RuntimePolicySetting: value, Configured: setting != nil} if setting != nil { result.UpdatedBy = setting.UpdatedBy result.CreatedAt = setting.CreatedAt result.UpdatedAt = setting.UpdatedAt } return result } func megabytes(value int64) int64 { return kernel.Megabytes(value) } func gigabytes(value int64) int64 { return kernel.Gigabytes(value) }