| 285 | } |
| 286 | |
| 287 | func defaultsValidateFile(file *defaultsFile) error { |
| 288 | var validationErrors []string |
| 289 | |
| 290 | validateNonZeroInt := func(field string, value *string) { |
| 291 | if value == nil { |
| 292 | return |
| 293 | } |
| 294 | trimmed := strings.TrimSpace(*value) |
| 295 | parsed, err := strconv.ParseInt(trimmed, 10, 64) |
| 296 | if err != nil || parsed == 0 { |
| 297 | validationErrors = append(validationErrors, field+" must be a non-zero integer when set") |
| 298 | } |
| 299 | } |
| 300 | validatePositiveInt := func(field string, value *string) { |
| 301 | if value == nil { |
| 302 | return |
| 303 | } |
| 304 | trimmed := strings.TrimSpace(*value) |
| 305 | parsed, err := strconv.ParseInt(trimmed, 10, 64) |
| 306 | if err != nil || parsed <= 0 { |
| 307 | validationErrors = append(validationErrors, field+" must be a positive integer when set") |
| 308 | } |
| 309 | } |
| 310 | validateNonEmpty := func(field string, value *string) { |
| 311 | if value == nil { |
| 312 | return |
| 313 | } |
| 314 | if strings.TrimSpace(*value) == "" { |
| 315 | validationErrors = append(validationErrors, field+" cannot be empty when set") |
| 316 | } |
| 317 | } |
| 318 | validateUTCOffset := func(field string, value *string) { |
| 319 | if value == nil { |
| 320 | return |
| 321 | } |
| 322 | if _, err := workflow.NormalizeUTCOffset(*value); err != nil { |
| 323 | validationErrors = append(validationErrors, field+" "+err.Error()) |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | validateNonZeroInt("default_max_ai_credits", file.DefaultMaxAICredits) |
| 328 | validatePositiveInt("default_max_turn_cache_misses", file.DefaultMaxTurnCacheMisses) |
| 329 | validateNonZeroInt("default_detection_max_ai_credits", file.DefaultDetectionMaxAICredits) |
| 330 | validateNonZeroInt("default_max_daily_ai_credits", file.DefaultMaxDailyAICredits) |
| 331 | validatePositiveInt("default_max_turns", file.DefaultMaxTurns) |
| 332 | validatePositiveInt("default_timeout_minutes", file.DefaultTimeoutMinutes) |
| 333 | validateNonEmpty("default_detection_model", file.DefaultDetectionModel) |
| 334 | validateUTCOffset("default_utc", file.DefaultUTC) |
| 335 | validateNonEmpty("default_model_copilot", file.DefaultModelCopilot) |
| 336 | validateNonEmpty("default_model_claude", file.DefaultModelClaude) |
| 337 | validateNonEmpty("default_model_codex", file.DefaultModelCodex) |
| 338 | |
| 339 | if len(validationErrors) > 0 { |
| 340 | return fmt.Errorf("invalid defaults file: %s", strings.Join(validationErrors, "; ")) |
| 341 | } |
| 342 | return nil |
| 343 | } |
| 344 | |