From 1d0e3e6ed65d3a43cbf9b8ceb9ff7f787e34dc71 Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 17:44:40 +0800 Subject: [PATCH 1/9] test: reproduce GPT Image 2 auto failover default conflict --- dto/gpt_image2_routing_profile_test.go | 47 +++++++++++++++++ model/channel_selection_test.go | 72 ++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index c9c9f75febe0..a76c5c728a07 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -56,3 +56,50 @@ func TestGPTImage2ProductionProfileValidatesAndRoutesTheCompleteMatrix(t *testin AspectRatio: "3:2", })) } + +func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaults(t *testing.T) { + profile := dto.ImageRoutingProfile{ + Model: "gpt-image-2", + Protocol: dto.ImageRoutingProtocolImagesGenerations, + UpstreamPath: "/v1/images/generations", + Operations: []dto.ImageOperation{dto.ImageOperationGeneration}, + Resolutions: []string{"1K", "2K"}, + AspectRatios: []string{"auto", "1:1"}, + Sizes: []string{"auto", "1024x1024", "1440x1440"}, + DefaultSize: "auto", + MaxOutputImages: 1, + VerificationStatus: dto.ImageRoutingVerificationProductionVerified, + AllowedCombinations: []dto.ImageRoutingCombination{ + {Operation: dto.ImageOperationGeneration, Size: "auto"}, + {Operation: dto.ImageOperationGeneration, Resolution: "1K", AspectRatio: "1:1", Size: "1024x1024"}, + {Operation: dto.ImageOperationGeneration, Resolution: "2K", AspectRatio: "1:1", Size: "1440x1440"}, + }, + } + config := &dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{profile}, + } + + require.NoError(t, config.Validate()) + + auto, err := profile.ApplyDefaults(dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Size: "auto", + N: 1, + }) + require.NoError(t, err) + assert.Equal(t, "", auto.Resolution) + assert.Equal(t, "", auto.AspectRatio) + assert.Equal(t, "auto", auto.Size) + + explicitAuto, err := profile.ApplyDefaults(dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Resolution: "1K", + AspectRatio: "auto", + N: 1, + }) + require.NoError(t, err) + assert.Equal(t, "1K", explicitAuto.Resolution) + assert.Equal(t, "auto", explicitAuto.AspectRatio) + assert.Equal(t, "auto", explicitAuto.Size) +} diff --git a/model/channel_selection_test.go b/model/channel_selection_test.go index d34dbf99f561..36e666cd030d 100644 --- a/model/channel_selection_test.go +++ b/model/channel_selection_test.go @@ -994,6 +994,78 @@ func TestGetRandomSatisfiedChannelRequiresExplicitVariantForConflictingDefaults( assert.Equal(t, 108, selected.Id) } +func TestGetRandomSatisfiedChannelKeepsContractAutoFailoverDefaultsCompatible(t *testing.T) { + setImageResolutionPricesForChannelSelectionTest(t) + oldMemoryCacheEnabled := common.MemoryCacheEnabled + common.MemoryCacheEnabled = true + ClearChannelCacheForTest() + clearChannelCooldownsForTest() + t.Cleanup(func() { + clearChannelCooldownsForTest() + ClearChannelCacheForTest() + common.MemoryCacheEnabled = oldMemoryCacheEnabled + }) + + primaryPriority := int64(10) + backupPriority := int64(9) + weight := uint(100) + primary := &Channel{Id: 117, Status: common.ChannelStatusEnabled, Weight: &weight, Priority: &primaryPriority} + backup := &Channel{Id: 127, Status: common.ChannelStatusEnabled, Weight: &weight, Priority: &backupPriority} + primary.SetOtherSettings(dto.ChannelOtherSettings{ImageRouting: &dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{{ + Model: "gpt-image-2", + Protocol: dto.ImageRoutingProtocolImagesGenerations, + UpstreamPath: "/v1/images/generations", + Operations: []dto.ImageOperation{dto.ImageOperationGeneration}, + Resolutions: []string{"1K", "2K"}, + AspectRatios: []string{"auto", "1:1"}, + Sizes: []string{"auto", "1024x1024", "1440x1440"}, + DefaultSize: "auto", + MaxOutputImages: 1, + VerificationStatus: dto.ImageRoutingVerificationProductionVerified, + AllowedCombinations: []dto.ImageRoutingCombination{ + {Operation: dto.ImageOperationGeneration, Size: "auto"}, + {Operation: dto.ImageOperationGeneration, Resolution: "1K", AspectRatio: "1:1", Size: "1024x1024"}, + {Operation: dto.ImageOperationGeneration, Resolution: "2K", AspectRatio: "1:1", Size: "1440x1440"}, + }, + }}, + }}) + backup.SetOtherSettings(dto.ChannelOtherSettings{ImageRouting: &dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{{ + Model: "gpt-image-2", + Protocol: dto.ImageRoutingProtocolKIEJobs, + UpstreamPath: "/api/v1/jobs/createTask", + Operations: []dto.ImageOperation{dto.ImageOperationGeneration}, + Sizes: []string{"auto"}, + DefaultSize: "auto", + MaxOutputImages: 1, + AllowedCombinations: []dto.ImageRoutingCombination{{ + Operation: dto.ImageOperationGeneration, + Size: "auto", + }}, + VerificationStatus: dto.ImageRoutingVerificationProductionVerified, + }}, + }}) + SetChannelCacheForTest(map[int]*Channel{117: primary, 127: backup}, map[string]map[string][]int{ + "gpt pro": {"gpt-image-2": {117, 127}}, + }) + + selected, err := GetRandomSatisfiedChannelWithOptions("gpt pro", "gpt-image-2", 0, ChannelSelectionOptions{ + ImageRequirement: &dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Size: "auto", + N: 1, + }, + RequestPath: "/v1/images/generations", + Path: "/v1/images/generations", + }) + require.NoError(t, err) + require.NotNil(t, selected) + assert.Equal(t, 117, selected.Id) +} + func TestGetRandomSatisfiedChannelRequiresExplicitTypedParameterForConflictingDefaults(t *testing.T) { setImageResolutionPricesForChannelSelectionTest(t) oldMemoryCacheEnabled := common.MemoryCacheEnabled From 53e68ef5e48472f34576b6ee5af0a92f30c0b462 Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 17:47:29 +0800 Subject: [PATCH 2/9] fix: preserve GPT Image 2 auto failover defaults --- dto/gpt_image2_routing_profile_test.go | 52 ++++++++++++++++++++++++++ dto/image_routing.go | 34 ++++++++++++++--- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index a76c5c728a07..7c9ede5d7a77 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -102,4 +102,56 @@ func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaul assert.Equal(t, "1K", explicitAuto.Resolution) assert.Equal(t, "auto", explicitAuto.AspectRatio) assert.Equal(t, "auto", explicitAuto.Size) + + negativeCases := []struct { + name string + mutate func(*dto.ImageRoutingProfile) + }{ + { + name: "another model cannot omit defaults", + mutate: func(candidate *dto.ImageRoutingProfile) { + candidate.Model = "future-image-model" + }, + }, + { + name: "image editing cannot omit defaults", + mutate: func(candidate *dto.ImageRoutingProfile) { + candidate.Protocol = dto.ImageRoutingProtocolImagesEdits + candidate.UpstreamPath = "/v1/images/edits" + candidate.Operations = []dto.ImageOperation{dto.ImageOperationEdit} + for index := range candidate.AllowedCombinations { + candidate.AllowedCombinations[index].Operation = dto.ImageOperationEdit + } + }, + }, + { + name: "mixed operations cannot omit defaults", + mutate: func(candidate *dto.ImageRoutingProfile) { + candidate.Operations = []dto.ImageOperation{dto.ImageOperationGeneration, dto.ImageOperationEdit} + }, + }, + { + name: "missing auto size default cannot omit defaults", + mutate: func(candidate *dto.ImageRoutingProfile) { + candidate.DefaultSize = "" + }, + }, + } + for _, testCase := range negativeCases { + t.Run(testCase.name, func(t *testing.T) { + candidate := profile + candidate.Operations = append([]dto.ImageOperation(nil), profile.Operations...) + candidate.AllowedCombinations = append( + []dto.ImageRoutingCombination(nil), + profile.AllowedCombinations..., + ) + testCase.mutate(&candidate) + err := (&dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{candidate}, + }).Validate() + require.Error(t, err) + assert.ErrorContains(t, err, "default_resolution is required") + }) + } } diff --git a/dto/image_routing.go b/dto/image_routing.go index 631d4fb67bef..56db24e7b8ab 100644 --- a/dto/image_routing.go +++ b/dto/image_routing.go @@ -501,6 +501,7 @@ func (profile *ImageRoutingProfile) validate(index int) error { {name: "default_quality", value: profile.DefaultQuality, declared: profile.Qualities, normalize: normalizeImageQuality}, {name: "default_output_format", value: profile.DefaultOutputFormat, declared: profile.OutputFormats, normalize: normalizeImageOutputFormat}, } + allowsContractAutoDefaults := profile.allowsGPTImage2ContractAutoDefaults() for _, item := range defaults { if item.value != item.normalize(item.value) { return fmt.Errorf("%s.%s must use a canonical value", prefix, item.name) @@ -511,6 +512,9 @@ func (profile *ImageRoutingProfile) validate(index int) error { } } if profile.VerificationStatus == ImageRoutingVerificationProductionVerified && len(item.declared) > 1 && item.value == "" { + if allowsContractAutoDefaults && (item.name == "default_resolution" || item.name == "default_aspect_ratio") { + continue + } return fmt.Errorf("%s.%s is required when a verified profile declares multiple values", prefix, item.name) } } @@ -667,11 +671,11 @@ func (profile *ImageRoutingProfile) validate(index int) error { for i, combination := range profile.AllowedCombinations { isVerifiedAutoGeometry := normalizeImageRoutingModel(profile.Model) == "gpt-image-2" && combination.Operation == ImageOperationGeneration && - combination.Resolution == "1K" && - combination.AspectRatio == "auto" && - combination.Size == "auto" - if combination.Resolution == "" || - (!imageSizePattern.MatchString(combination.Size) && !isVerifiedAutoGeometry) { + combination.Size == "auto" && + ((combination.Resolution == "1K" && combination.AspectRatio == "auto") || + (combination.Resolution == "" && combination.AspectRatio == "")) + if !isVerifiedAutoGeometry && + (combination.Resolution == "" || !imageSizePattern.MatchString(combination.Size)) { return fmt.Errorf("%s.allowed_combinations[%d] must bind resolution to an exact size for output verification", prefix, i) } coveredResolutions[combination.Resolution] = struct{}{} @@ -709,6 +713,26 @@ func (profile *ImageRoutingProfile) validate(index int) error { return nil } +func (profile *ImageRoutingProfile) allowsGPTImage2ContractAutoDefaults() bool { + if profile == nil || normalizeImageRoutingModel(profile.Model) != "gpt-image-2" || + profile.VerificationStatus != ImageRoutingVerificationProductionVerified || + len(profile.Operations) != 1 || profile.Operations[0] != ImageOperationGeneration || + profile.DefaultSize != "auto" { + return false + } + for _, combination := range profile.AllowedCombinations { + if combination.Operation == ImageOperationGeneration && + combination.Resolution == "" && + combination.AspectRatio == "" && + combination.Size == "auto" && + combination.Quality == "" && + combination.OutputFormat == "" { + return true + } + } + return false +} + func validateImageRoutingCombinationCoverage( prefix string, field string, From 553b96e515be8286740dc2518fc87f34530444cd Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 17:54:17 +0800 Subject: [PATCH 3/9] test: reject fixed tuples through GPT Image 2 auto sentinel --- dto/gpt_image2_routing_profile_test.go | 17 +++++++++++++++-- model/channel_selection_test.go | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index 7c9ede5d7a77..b03a3d1d169b 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -52,8 +52,8 @@ func TestGPTImage2ProductionProfileValidatesAndRoutesTheCompleteMatrix(t *testin } assert.False(t, config.Supports("gpt-image-2", dto.ImageSelectionRequirement{ Operation: dto.ImageOperationGeneration, - Resolution: "4K", - AspectRatio: "3:2", + Resolution: "2K", + AspectRatio: "auto", })) } @@ -102,6 +102,19 @@ func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaul assert.Equal(t, "1K", explicitAuto.Resolution) assert.Equal(t, "auto", explicitAuto.AspectRatio) assert.Equal(t, "auto", explicitAuto.Size) + assert.False(t, config.Supports("gpt-image-2", dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Resolution: "2K", + AspectRatio: "auto", + Size: "auto", + N: 1, + })) + assert.False(t, config.Supports("gpt-image-2", dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Resolution: "4K", + AspectRatio: "3:2", + N: 1, + })) negativeCases := []struct { name string diff --git a/model/channel_selection_test.go b/model/channel_selection_test.go index 36e666cd030d..983dd6a876cc 100644 --- a/model/channel_selection_test.go +++ b/model/channel_selection_test.go @@ -1064,6 +1064,20 @@ func TestGetRandomSatisfiedChannelKeepsContractAutoFailoverDefaultsCompatible(t require.NoError(t, err) require.NotNil(t, selected) assert.Equal(t, 117, selected.Id) + + selected, err = GetRandomSatisfiedChannelWithOptions("gpt pro", "gpt-image-2", 0, ChannelSelectionOptions{ + ExcludedChannelIDs: map[int]struct{}{117: {}}, + ImageRequirement: &dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Size: "auto", + N: 1, + }, + RequestPath: "/v1/images/generations", + Path: "/v1/images/generations", + }) + require.NoError(t, err) + require.NotNil(t, selected) + assert.Equal(t, 127, selected.Id) } func TestGetRandomSatisfiedChannelRequiresExplicitTypedParameterForConflictingDefaults(t *testing.T) { From 5691a2784cb0f28ea43a0aaa375f9614cd68e6f8 Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 17:56:41 +0800 Subject: [PATCH 4/9] test: constrain GPT Image 2 auto geometry exception --- dto/gpt_image2_routing_profile_test.go | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index b03a3d1d169b..3373896026e5 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -167,4 +167,45 @@ func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaul assert.ErrorContains(t, err, "default_resolution is required") }) } + + t.Run("mixed operations cannot restore the contract auto sentinel with explicit defaults", func(t *testing.T) { + candidate := profile + candidate.Operations = []dto.ImageOperation{dto.ImageOperationGeneration, dto.ImageOperationEdit} + candidate.DefaultResolution = "1K" + candidate.DefaultAspectRatio = "auto" + candidate.AllowedCombinations = append( + append([]dto.ImageRoutingCombination(nil), profile.AllowedCombinations...), + dto.ImageRoutingCombination{ + Operation: dto.ImageOperationEdit, + Resolution: "1K", + AspectRatio: "1:1", + Size: "1024x1024", + }, + ) + err := (&dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{candidate}, + }).Validate() + require.Error(t, err) + assert.ErrorContains(t, err, "exact size") + }) + + t.Run("contract auto sentinel cannot carry output controls", func(t *testing.T) { + candidate := profile + candidate.DefaultResolution = "1K" + candidate.DefaultAspectRatio = "auto" + candidate.OutputFormats = []string{"png"} + candidate.DefaultOutputFormat = "png" + candidate.AllowedCombinations = append( + []dto.ImageRoutingCombination(nil), + profile.AllowedCombinations..., + ) + candidate.AllowedCombinations[0].OutputFormat = "png" + err := (&dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{candidate}, + }).Validate() + require.Error(t, err) + assert.ErrorContains(t, err, "exact size") + }) } From af5968f634c92118c659d34c4dbf6c160f354813 Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 17:57:40 +0800 Subject: [PATCH 5/9] fix: make GPT Image 2 auto sentinel absent-only --- dto/image_routing.go | 61 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/dto/image_routing.go b/dto/image_routing.go index 56db24e7b8ab..848d9e8b9c3e 100644 --- a/dto/image_routing.go +++ b/dto/image_routing.go @@ -318,7 +318,7 @@ func (config *ImageRoutingConfig) Supports(model string, requirement ImageSelect return true } for _, combination := range profile.AllowedCombinations { - if combination.matches(normalized) { + if profile.allowedCombinationMatches(combination, normalized) { return true } } @@ -381,7 +381,7 @@ func (profile *ImageRoutingProfile) inferUniqueCombinationValues(requirement *Im for range 5 { candidates := make([]ImageRoutingCombination, 0, len(profile.AllowedCombinations)) for _, combination := range profile.AllowedCombinations { - if combination.matches(*requirement) { + if profile.allowedCombinationMatches(combination, *requirement) { candidates = append(candidates, combination) } } @@ -655,7 +655,7 @@ func (profile *ImageRoutingProfile) validate(index int) error { } matched := false for _, combination := range profile.AllowedCombinations { - if combination.matches(defaultRequirement) { + if profile.allowedCombinationMatches(combination, defaultRequirement) { matched = true break } @@ -669,11 +669,7 @@ func (profile *ImageRoutingProfile) validate(index int) error { coveredResolutions := make(map[string]struct{}, len(profile.Resolutions)) coveredSizes := make(map[string]struct{}, len(profile.Sizes)) for i, combination := range profile.AllowedCombinations { - isVerifiedAutoGeometry := normalizeImageRoutingModel(profile.Model) == "gpt-image-2" && - combination.Operation == ImageOperationGeneration && - combination.Size == "auto" && - ((combination.Resolution == "1K" && combination.AspectRatio == "auto") || - (combination.Resolution == "" && combination.AspectRatio == "")) + isVerifiedAutoGeometry := profile.isVerifiedGPTImage2AutoGeometry(combination) if !isVerifiedAutoGeometry && (combination.Resolution == "" || !imageSizePattern.MatchString(combination.Size)) { return fmt.Errorf("%s.allowed_combinations[%d] must bind resolution to an exact size for output verification", prefix, i) @@ -721,18 +717,55 @@ func (profile *ImageRoutingProfile) allowsGPTImage2ContractAutoDefaults() bool { return false } for _, combination := range profile.AllowedCombinations { - if combination.Operation == ImageOperationGeneration && - combination.Resolution == "" && - combination.AspectRatio == "" && - combination.Size == "auto" && - combination.Quality == "" && - combination.OutputFormat == "" { + if isGPTImage2ContractAutoCombination(combination) { return true } } return false } +func (profile *ImageRoutingProfile) allowedCombinationMatches( + combination ImageRoutingCombination, + requirement ImageSelectionRequirement, +) bool { + if !combination.matches(requirement) { + return false + } + if !profile.allowsGPTImage2ContractAutoDefaults() || + !isGPTImage2ContractAutoCombination(combination) { + return true + } + return (requirement.Resolution == "" || requirement.Resolution == "1K") && + (requirement.AspectRatio == "" || requirement.AspectRatio == "auto") && + (requirement.Size == "" || requirement.Size == "auto") +} + +func isGPTImage2ContractAutoCombination(combination ImageRoutingCombination) bool { + return combination.Operation == ImageOperationGeneration && + combination.Resolution == "" && + combination.AspectRatio == "" && + combination.Size == "auto" && + combination.Quality == "" && + combination.OutputFormat == "" +} + +func (profile *ImageRoutingProfile) isVerifiedGPTImage2AutoGeometry( + combination ImageRoutingCombination, +) bool { + if profile == nil || normalizeImageRoutingModel(profile.Model) != "gpt-image-2" || + combination.Operation != ImageOperationGeneration || + combination.Size != "auto" || + combination.Quality != "" || + combination.OutputFormat != "" { + return false + } + if combination.Resolution == "1K" && combination.AspectRatio == "auto" { + return true + } + return profile.allowsGPTImage2ContractAutoDefaults() && + isGPTImage2ContractAutoCombination(combination) +} + func validateImageRoutingCombinationCoverage( prefix string, field string, From 4f1fde03732b7357607a7e0c5885df10091343d7 Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 18:04:18 +0800 Subject: [PATCH 6/9] test: reject output controls on GPT Image 2 auto sentinel --- dto/gpt_image2_routing_profile_test.go | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index 3373896026e5..af27c6605a44 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -208,4 +208,59 @@ func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaul require.Error(t, err) assert.ErrorContains(t, err, "exact size") }) + + for _, testCase := range []struct { + name string + configure func(*dto.ImageRoutingProfile) + requirement dto.ImageSelectionRequirement + }{ + { + name: "contract auto sentinel cannot borrow a declared quality", + configure: func(candidate *dto.ImageRoutingProfile) { + candidate.Qualities = []string{"high"} + candidate.DefaultQuality = "high" + for index := 1; index < len(candidate.AllowedCombinations); index++ { + candidate.AllowedCombinations[index].Quality = "high" + } + }, + requirement: dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Size: "auto", + Quality: "high", + N: 1, + }, + }, + { + name: "contract auto sentinel cannot borrow a declared output format", + configure: func(candidate *dto.ImageRoutingProfile) { + candidate.OutputFormats = []string{"png"} + candidate.DefaultOutputFormat = "png" + for index := 1; index < len(candidate.AllowedCombinations); index++ { + candidate.AllowedCombinations[index].OutputFormat = "png" + } + }, + requirement: dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Size: "auto", + OutputFormat: "png", + N: 1, + }, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + candidate := profile + candidate.AllowedCombinations = append( + []dto.ImageRoutingCombination(nil), + profile.AllowedCombinations..., + ) + testCase.configure(&candidate) + config := &dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{candidate}, + } + + require.NoError(t, config.Validate()) + assert.False(t, config.Supports("gpt-image-2", testCase.requirement)) + }) + } } From c2c1393c17ff2dfe01cb468ddff2109191dce460 Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 18:05:40 +0800 Subject: [PATCH 7/9] fix: forbid output controls on GPT Image 2 auto sentinel --- dto/gpt_image2_routing_profile_test.go | 4 +++- dto/image_routing.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index af27c6605a44..d9e97bbd7bb7 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -259,7 +259,9 @@ func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaul Profiles: []dto.ImageRoutingProfile{candidate}, } - require.NoError(t, config.Validate()) + err := config.Validate() + require.Error(t, err) + assert.ErrorContains(t, err, "default image tuple") assert.False(t, config.Supports("gpt-image-2", testCase.requirement)) }) } diff --git a/dto/image_routing.go b/dto/image_routing.go index 848d9e8b9c3e..e52297557f2e 100644 --- a/dto/image_routing.go +++ b/dto/image_routing.go @@ -737,7 +737,9 @@ func (profile *ImageRoutingProfile) allowedCombinationMatches( } return (requirement.Resolution == "" || requirement.Resolution == "1K") && (requirement.AspectRatio == "" || requirement.AspectRatio == "auto") && - (requirement.Size == "" || requirement.Size == "auto") + (requirement.Size == "" || requirement.Size == "auto") && + requirement.Quality == "" && + requirement.OutputFormat == "" } func isGPTImage2ContractAutoCombination(combination ImageRoutingCombination) bool { From 3161df8d4ed0c0f86c0918c81553dad11a444e3c Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 18:12:30 +0800 Subject: [PATCH 8/9] test: cover exact GPT Image 2 auto output controls --- dto/gpt_image2_routing_profile_test.go | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/dto/gpt_image2_routing_profile_test.go b/dto/gpt_image2_routing_profile_test.go index d9e97bbd7bb7..41cfec5c33e1 100644 --- a/dto/gpt_image2_routing_profile_test.go +++ b/dto/gpt_image2_routing_profile_test.go @@ -265,4 +265,73 @@ func TestGPTImage2ProductionProfileAllowsContractAutoTupleWithoutInventingDefaul assert.False(t, config.Supports("gpt-image-2", testCase.requirement)) }) } + + for _, testCase := range []struct { + name string + configure func(*dto.ImageRoutingProfile) + requirement dto.ImageSelectionRequirement + }{ + { + name: "exact auto geometry cannot borrow a declared quality", + configure: func(candidate *dto.ImageRoutingProfile) { + candidate.Qualities = []string{"high"} + candidate.DefaultQuality = "high" + for index := 1; index < len(candidate.AllowedCombinations); index++ { + candidate.AllowedCombinations[index].Quality = "high" + } + }, + requirement: dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Resolution: "1K", + AspectRatio: "auto", + Size: "auto", + Quality: "high", + N: 1, + }, + }, + { + name: "exact auto geometry cannot borrow a declared output format", + configure: func(candidate *dto.ImageRoutingProfile) { + candidate.OutputFormats = []string{"png"} + candidate.DefaultOutputFormat = "png" + for index := 1; index < len(candidate.AllowedCombinations); index++ { + candidate.AllowedCombinations[index].OutputFormat = "png" + } + }, + requirement: dto.ImageSelectionRequirement{ + Operation: dto.ImageOperationGeneration, + Resolution: "1K", + AspectRatio: "auto", + Size: "auto", + OutputFormat: "png", + N: 1, + }, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + candidate := profile + candidate.DefaultResolution = "1K" + candidate.DefaultAspectRatio = "auto" + candidate.AllowedCombinations = append( + []dto.ImageRoutingCombination(nil), + profile.AllowedCombinations..., + ) + candidate.AllowedCombinations[0] = dto.ImageRoutingCombination{ + Operation: dto.ImageOperationGeneration, + Resolution: "1K", + AspectRatio: "auto", + Size: "auto", + } + testCase.configure(&candidate) + config := &dto.ImageRoutingConfig{ + Version: dto.ImageRoutingVersion1, + Profiles: []dto.ImageRoutingProfile{candidate}, + } + + err := config.Validate() + require.Error(t, err) + assert.ErrorContains(t, err, "default image tuple") + assert.False(t, config.Supports("gpt-image-2", testCase.requirement)) + }) + } } From 8e96b1ab2afd0f1fcc12331184100c0005eccf3d Mon Sep 17 00:00:00 2001 From: chenlingzhi Date: Sun, 2 Aug 2026 18:13:16 +0800 Subject: [PATCH 9/9] fix: constrain exact GPT Image 2 auto output controls --- dto/image_routing.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dto/image_routing.go b/dto/image_routing.go index e52297557f2e..305fbe6ab5b8 100644 --- a/dto/image_routing.go +++ b/dto/image_routing.go @@ -731,15 +731,19 @@ func (profile *ImageRoutingProfile) allowedCombinationMatches( if !combination.matches(requirement) { return false } - if !profile.allowsGPTImage2ContractAutoDefaults() || - !isGPTImage2ContractAutoCombination(combination) { + if profile.VerificationStatus != ImageRoutingVerificationProductionVerified || + !profile.isVerifiedGPTImage2AutoGeometry(combination) { + return true + } + if requirement.Quality != "" || requirement.OutputFormat != "" { + return false + } + if !isGPTImage2ContractAutoCombination(combination) { return true } return (requirement.Resolution == "" || requirement.Resolution == "1K") && (requirement.AspectRatio == "" || requirement.AspectRatio == "auto") && - (requirement.Size == "" || requirement.Size == "auto") && - requirement.Quality == "" && - requirement.OutputFormat == "" + (requirement.Size == "" || requirement.Size == "auto") } func isGPTImage2ContractAutoCombination(combination ImageRoutingCombination) bool {