diff --git a/src/shared/validation/schemas/settings.ts b/src/shared/validation/schemas/settings.ts index a57b349cc8..75e813c93b 100644 --- a/src/shared/validation/schemas/settings.ts +++ b/src/shared/validation/schemas/settings.ts @@ -101,7 +101,20 @@ export const providerCooldownSettingsSchema = z minRetryCooldownMs: z.number().int().min(0).max(300000).optional(), maxRetryCooldownMs: z.number().int().min(0).max(3600000).optional(), }) - .strict(); + .strict() + .superRefine((value, ctx) => { + if ( + typeof value.minRetryCooldownMs === "number" && + typeof value.maxRetryCooldownMs === "number" && + value.maxRetryCooldownMs < value.minRetryCooldownMs + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "maxRetryCooldownMs must be greater than or equal to minRetryCooldownMs", + path: ["maxRetryCooldownMs"], + }); + } + }); export const updateResilienceSchema = z .object({ diff --git a/tests/unit/resilience-provider-cooldown-api-3556.test.ts b/tests/unit/resilience-provider-cooldown-api-3556.test.ts index 7de0718772..88b08a07c3 100644 --- a/tests/unit/resilience-provider-cooldown-api-3556.test.ts +++ b/tests/unit/resilience-provider-cooldown-api-3556.test.ts @@ -41,6 +41,16 @@ describe("providerCooldown in updateResilienceSchema", () => { }); assert.equal(result.success, false, "Schema should reject unknown keys"); }); + + it("rejects providerCooldown max below min", () => { + const result = updateResilienceSchema.safeParse({ + providerCooldown: { + minRetryCooldownMs: 120000, + maxRetryCooldownMs: 30000, + }, + }); + assert.equal(result.success, false, "Schema should reject contradictory cooldown bounds"); + }); }); describe("providerCooldown roundtrip through mergeResilienceSettings", () => {