Code change:
expectedIPFamilies:     nil, expectError:            true, }, <ins> // preferDualStack services should not be updated // to match cluster config if the user didn't change any // ClusterIPs related fields { name:       "unchanged preferDualStack-1-ClusterUpgraded", modifyRest: fnMakeDualStackStackIPv4IPv6Allocator, oldSvc: &api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol}, IPFamilyPolicy: &preferDualStack, }, }, svc: api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol}, IPFamilyPolicy: &preferDualStack, }, }, expectedIPFamilyPolicy: &preferDualStack, expectedIPFamilies:     []api.IPFamily{api.IPv4Protocol}, expectError:            false, }, { name:       "unchanged preferDualStack-2-ClusterDowngraded", modifyRest: fnMakeSingleStackIPv4Allocator, oldSvc: &api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1", "2001::1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, IPFamilyPolicy: &preferDualStack, }, }, svc: api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1", "2001::1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, IPFamilyPolicy: &preferDualStack, }, }, expectedIPFamilyPolicy: &preferDualStack, expectedIPFamilies:     []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, expectError:            false, }, { name:       "changed preferDualStack-1 (cluster upgraded)", modifyRest: fnMakeDualStackStackIPv4IPv6Allocator, oldSvc: &api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol}, IPFamilyPolicy: &preferDualStack, }, }, svc: api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     nil, IPFamilies:     []api.IPFamily{api.IPv4Protocol}, IPFamilyPolicy: &requireDualStack, }, }, expectedIPFamilyPolicy: &requireDualStack, expectedIPFamilies:     []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, expectError:            false, }, { name:       "changed preferDualStack-2-ClusterDowngraded", modifyRest: fnMakeSingleStackIPv4Allocator, oldSvc: &api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1", "2001::1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, IPFamilyPolicy: &preferDualStack, }, }, svc: api.Service{ Spec: api.ServiceSpec{ Type:           api.ServiceTypeClusterIP, ClusterIPs:     []string{"1.1.1.1"}, IPFamilies:     []api.IPFamily{api.IPv4Protocol}, IPFamilyPolicy: &singleStack, }, }, expectedIPFamilyPolicy: &singleStack, expectedIPFamilies:     []api.IPFamily{api.IPv4Protocol}, expectError:            false, }, </ins> } // This func only runs when feature gate is on

Related discussion:
text: 1) start with a single stack cluster 2) create a PreferDualStack service 3) configure API server with a 2nd service CIDR 4) Confirm the above service is not dual-stack 5) Add a label to the above service 6) Observe that the service becomes dual-stack I think the correct answer is to have a "repair loop" that upgrades (and downgrades!) PreferDualStack Services to match the config. I think this is a GA blocker.
/triage accepted The current thought is: of type with are special new breed of api objects that I don't think we have any similar precedent in the apis, i could be wrong. Their behavior is tied to how the cluster is configured. Thus their behavior must change as the cluster behavior change. a way the user is stating . If it is cluster then i am good with two ClusterIPs. If it is single stack cluster then I am also good with a single ClusterIP. One can assume that if the user was ok was with getting a additional IP when possible then they must be ok with taking it away when it is no longer possible. Based on the above I believe best course of action is create a new repair loop or piggyback on any of the existing (or better: fix it in rest stack during list and get calls). The new logic takes care of matching to the .
reproducer and regression test if somebody takes this, feel free to add it
ack - Scheduled to tackle 3rd week of May. Will definitely include that patch.