Repository: kubernetes/kubernetes (language: go)

Issue:
Ratcheting validation missing for CRD status subresources
### What happened?

Testing the new CRD [ratcheting validation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-ratcheting) feature, I implemented a change of validation from `pattern` to `enum` in both a `spec` and `status` field on a CRD. Note, this CRD includes a status subresource.

I created the CRD with the `pattern` validation on the fields initially and then created a CR with field values in spec and status that matched the pattern, but did not match my intended enum.

Editing the CRD now to move to the `enum`, this would invalidate the stored data in the two fields, meaning ratcheting is required.

Updating fields such as metadata (adding an annotation) or other spec fields worked correctly, and no issues were returned.

Trying to update any status field using the status subresource path failed, complaining that my field did not match the expected enum.

### What did you expect to happen?

Values to the status should be ratcheted in the same way that the based endpoint is ratcheted.

### How can we reproduce it (as minimally and precisely as possible)?

1. Create a minimal CRD with a status subresource and a field with a pattern validation (and another field for testing)
2. Persist a CR with a value that does not match the pattern.
3. Update the CRD schema to move from `pattern` to `enum` with a value that does not match the persisted CR.
4. Update an annotation, observe success
5. Update the other CRD status field with a new value, observe that it errors complaining about the enum value being incorrect.

### Anything else we need to know?

Neither the [documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-ratcheting) nor the [KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/4008-crd-ratcheting) suggest that there should be any difference in behaviour.

I raised this [on slack](https://kubernetes.slack.com/archives/C0EG7JC6T/p1736187102623779) and it was suggested that this was a bug.

Looking at the code, I can see [ratcheting options](https://github.com/kubernetes/apiextensions-apiserver/blob/d72b6f721f9b3cf655cb545e59cfb5f4bc615b8f/pkg/registry/customresource/strategy.go#L287-L291) on the storage for the main path, but not in the [status strategy](https://github.com/kubernetes/apiextensions-apiserver/blob/d72b6f721f9b3cf655cb545e59cfb5f4bc615b8f/pkg/registry/customresource/status_strategy.go#L96)

### Kubernetes version

<details>
Still in the main development branch AFAICT, tested on 1.31.3
```console
$ kubectl version
# paste output here
```

</details>


### Cloud provider

<details>
N/A
</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
# paste output here

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>



Discussion / hints:
This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
/sig api-machinery
/assign

Fix patch:
diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go
index ceaf56629b08f..ea5a412af59fa 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go
@@ -22,11 +22,17 @@ import (
 
 	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
 
+	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
+	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
 	structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
+	"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
+	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/validation/field"
 	celconfig "k8s.io/apiserver/pkg/apis/cel"
+	"k8s.io/apiserver/pkg/cel/common"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
 )
 
 type statusStrategy struct {
@@ -94,8 +100,17 @@ func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
 		return field.ErrorList{field.Invalid(field.NewPath(""), old, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", old))}
 	}
 
+	var options []validation.ValidationOption
+	var celOptions []cel.Option
+	var correlatedObject *common.CorrelatedObject
+	if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
+		correlatedObject = common.NewCorrelatedObject(uNew.Object, uOld.Object, &model.Structural{Structural: a.structuralSchema})
+		options = append(options, validation.WithRatcheting(correlatedObject.Key("status")))
+		celOptions = append(celOptions, cel.WithRatcheting(correlatedObject))
+	}
+
 	var errs field.ErrorList
-	errs = append(errs, a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, uNew, uOld, a.scale)...)
+	errs = append(errs, a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, uNew, uOld, a.scale, options...)...)
 
 	// ratcheting validation of x-kubernetes-list-type value map and set
 	if newErrs := structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchema, uNew.Object); len(newErrs) > 0 {
@@ -109,10 +124,15 @@ func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
 		if has, err := hasBlockingErr(errs); has {
 			errs = append(errs, err)
 		} else {
-			err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchema, uNew.Object, uOld.Object, celconfig.RuntimeCELCostBudget)
+			err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchema, uNew.Object, uOld.Object, celconfig.RuntimeCELCostBudget, celOptions...)
 			errs = append(errs, err...)
 		}
 	}
+
+	// No-op if not attached to context
+	if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
+		validation.Metrics.ObserveRatchetingTime(*correlatedObject.Duration)
+	}
 	return errs
 }
 
diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
index 7e2f6f4d551b0..eabb3fd572dbf 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
@@ -96,7 +96,7 @@ func validateKubeFinalizerName(stringValue string, fldPath *field.Path) []string
 	return allWarnings
 }
 
-func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
+func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale, options ...apiextensionsvalidation.ValidationOption) field.ErrorList {
 	if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
 		return errs
 	}
@@ -105,7 +105,7 @@ func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj,
 
 	allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
 	if status, hasStatus := obj.UnstructuredContent()["status"]; hasStatus {
-		allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(field.NewPath("status"), status, old.UnstructuredContent()["status"], a.statusSchemaValidator)...)
+		allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(field.NewPath("status"), status, old.UnstructuredContent()["status"], a.statusSchemaValidator, options...)...)
 	}
 	allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
 


Test patch:
diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/ratcheting_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/ratcheting_test.go
index e39f0d7bb40a9..e71759d4b50a8 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/ratcheting_test.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/ratcheting_test.go
@@ -81,6 +81,7 @@ type ratchetingTestContext struct {
 	*testing.T
 	DynamicClient       dynamic.Interface
 	APIExtensionsClient clientset.Interface
+	StatusSubresource   bool
 }
 
 type ratchetingTestOperation interface {
@@ -164,7 +165,7 @@ func (a applyPatchOperation) Do(ctx *ratchetingTestContext) error {
 
 	patch := &unstructured.Unstructured{}
 	if obj, ok := a.patch.(map[string]interface{}); ok {
-		patch.Object = obj
+		patch.Object = runtime.DeepCopyJSON(obj)
 	} else if str, ok := a.patch.(string); ok {
 		str = FixTabsOrDie(str)
 		if err := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(str), len(str)).Decode(&patch.Object); err != nil {
@@ -174,24 +175,31 @@ func (a applyPatchOperation) Do(ctx *ratchetingTestContext) error {
 		return fmt.Errorf("invalid patch type: %T", a.patch)
 	}
 
+	if ctx.StatusSubresource {
+		patch.Object = map[string]interface{}{"status": patch.Object}
+	}
+
 	patch.SetKind(kind)
 	patch.SetAPIVersion(a.gvr.GroupVersion().String())
 	patch.SetName(a.name)
 	patch.SetNamespace("default")
 
-	_, err := ctx.DynamicClient.
-		Resource(a.gvr).
-		Namespace(patch.GetNamespace()).
-		Apply(
-			context.TODO(),
-			patch.GetName(),
-			patch,
-			metav1.ApplyOptions{
-				FieldManager: "manager",
-			})
+	c := ctx.DynamicClient.Resource(a.gvr).Namespace(patch.GetNamespace())
+	if ctx.StatusSubresource {
+		if _, err := c.Get(context.TODO(), patch.GetName(), metav1.GetOptions{}); apierrors.IsNotFound(err) {
+			// ApplyStatus will not automatically create an object, we must make sure it exists before we can
+			// apply the status to it.
+			_, err := c.Create(context.TODO(), patch, metav1.CreateOptions{})
+			if err != nil {
+				return err
+			}
+		}
 
+		_, err := c.ApplyStatus(context.TODO(), patch.GetName(), patch, metav1.ApplyOptions{FieldManager: "manager"})
+		return err
+	}
+	_, err := c.Apply(context.TODO(), patch.GetName(), patch, metav1.ApplyOptions{FieldManager: "manager"})
 	return err
-
 }
 
 func (a applyPatchOperation) Description() string {
@@ -228,10 +236,20 @@ func (u updateMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
 			}},
 		}
 
+		if ctx.StatusSubresource {
+			sch = &apiextensionsv1.JSONSchemaProps{
+				Type: "object",
+				Properties: map[string]apiextensionsv1.JSONSchemaProps{
+					"status": *sch,
+				},
+			}
+		}
+
 		for _, v := range myCRD.Spec.Versions {
 			if v.Name != myCRDV1Beta1.Version {
 				continue
 			}
+
 			v.Schema.OpenAPIV3Schema = sch
 		}
 
@@ -282,8 +300,17 @@ type patchMyCRDV1Beta1Schema struct {
 }
 
 func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
+	patch := p.patch
+	if ctx.StatusSubresource {
+		patch = map[string]interface{}{
+			"properties": map[string]interface{}{
+				"status": patch,
+			},
+		}
+	}
+
 	var err error
-	patchJSON, err := json.Marshal(p.patch)
+	patchJSON, err := json.Marshal(patch)
 	if err != nil {
 		return err
 	}
@@ -315,7 +342,12 @@ func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
 
 		return updateMyCRDV1Beta1Schema{
 			newSchema: &parsed,
-		}.Do(ctx)
+		}.Do(&ratchetingTestContext{
+			T:                   ctx.T,
+			DynamicClient:       ctx.DynamicClient,
+			APIExtensionsClient: ctx.APIExtensionsClient,
+			StatusSubresource:   false, // We have already handled the status subresource.
+		})
 	}
 
 	return fmt.Errorf("could not find version %v in CRD %v", myCRDV1Beta1.Version, myCRD.Name)
@@ -329,6 +361,7 @@ type ratchetingTestCase struct {
 	Name       string
 	Disabled   bool
 	Operations []ratchetingTestOperation
+	SkipStatus bool
 }
 
 func runTests(t *testing.T, cases []ratchetingTestCase) {
@@ -372,9 +405,15 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
 									},
 								},
 							},
+							"status": {
+								Type: "object",
+							},
 						},
 					},
 				},
+				Subresources: &apiextensionsv1.CustomResourceSubresources{
+					Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
+				},
 			}},
 			Names: apiextensionsv1.CustomResourceDefinitionNames{
 				Plural:   resource,
@@ -394,13 +433,7 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
 			continue
 		}
 
-		t.Run(c.Name, func(t *testing.T) {
-			ctx := &ratchetingTestContext{
-				T:                   t,
-				DynamicClient:       dynamicClient,
-				APIExtensionsClient: apiExtensionClient,
-			}
-
+		run := func(t *testing.T, ctx *ratchetingTestContext) {
 			for i, op := range c.Operations {
 				t.Logf("Performing Operation: %v", op.Description())
 				if err := op.Do(ctx); err != nil {
@@ -413,7 +446,26 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
 			if err != nil {
 				t.Fatal(err)
 			}
+		}
+
+		t.Run(c.Name, func(t *testing.T) {
+			run(t, &ratchetingTestContext{
+				T:                   t,
+				DynamicClient:       dynamicClient,
+				APIExtensionsClient: apiExtensionClient,
+			})
 		})
+
+		if !c.SkipStatus {
+			t.Run("Status: "+c.Name, func(t *testing.T) {
+				run(t, &ratchetingTestContext{
+					T:                   t,
+					DynamicClient:       dynamicClient,
+					APIExtensionsClient: apiExtensionClient,
+					StatusSubresource:   true,
+				})
+			})
+		}
 	}
 }
 
@@ -443,23 +495,23 @@ func TestRatchetingFunctionality(t *testing.T) {
 					myCRDV1Beta1,
 					myCRDInstanceName,
 					map[string]interface{}{
-						"hasMinimum":           0,
-						"hasMaximum":           1000,
-						"hasMinimumAndMaximum": 50,
+						"hasMinimum":           int64(0),
+						"hasMaximum":           int64(1000),
+						"hasMinimumAndMaximum": int64(50),
 					}},
 				patchMyCRDV1Beta1Schema{
 					"Add stricter minimums and maximums that violate the previous object",
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"hasMinimum": map[string]interface{}{
-								"minimum": 10,
+								"minimum": int64(10),
 							},
 							"hasMaximum": map[string]interface{}{
-								"maximum": 20,
+								"maximum": int64(20),
 							},
 							"hasMinimumAndMaximum": map[string]interface{}{
-								"minimum": 10,
-								"maximum": 20,
+								"minimum": int64(10),
+								"maximum": int64(20),
 							},
 							"noRestrictions": map[string]interface{}{
 								"type": "integer",
@@ -471,33 +523,33 @@ func TestRatchetingFunctionality(t *testing.T) {
 					myCRDV1Beta1,
 					myCRDInstanceName,
 					map[string]interface{}{
-						"noRestrictions": 50,
+						"noRestrictions": int64(50),
 					}},
 				expectError{
 					applyPatchOperation{
 						"Change a single old field to be invalid",
 						myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
-							"hasMinimum": 5,
+							"hasMinimum": int64(5),
 						}},
 				},
 				expectError{
 					applyPatchOperation{
 						"Change multiple old fields to be invalid",
 						myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
-							"hasMinimum": 5,
-							"hasMaximum": 21,
+							"hasMinimum": int64(5),
+							"hasMaximum": int64(21),
 						}},
 				},
 				applyPatchOperation{
 					"Change single old field to be valid",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
-						"hasMinimum": 11,
+						"hasMinimum": int64(11),
 					}},
 				applyPatchOperation{
 					"Change multiple old fields to be valid",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
-						"hasMaximum":           19,
-						"hasMinimumAndMaximum": 15,
+						"hasMaximum":           int64(19),
+						"hasMinimumAndMaximum": int64(15),
 					}},
 			},
 		},
@@ -576,8 +628,8 @@ func TestRatchetingFunctionality(t *testing.T) {
 					"Create an instance",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 						"nums": map[string]interface{}{
-							"num1": 1,
-							"num2": 1000000,
+							"num1": int64(1),
+							"num2": int64(1000000),
 						},
 						"content": map[string]interface{}{
 							"k1": "some content",
@@ -590,7 +642,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"properties": map[string]interface{}{
 							"nums": map[string]interface{}{
 								"additionalProperties": map[string]interface{}{
-									"minimum": 1000,
+									"minimum": int64(1000),
 								},
 							},
 						},
@@ -599,16 +651,16 @@ func TestRatchetingFunctionality(t *testing.T) {
 					"updating validating field num2 to another validating value, but rachet invalid field num1",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 						"nums": map[string]interface{}{
-							"num1": 1,
-							"num2": 2000,
+							"num1": int64(1),
+							"num2": int64(2000),
 						},
 					}},
 				expectError{applyPatchOperation{
 					"update field num1 to different invalid value",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 						"nums": map[string]interface{}{
-							"num1": 2,
-							"num2": 2000,
+							"num1": int64(2),
+							"num2": int64(2000),
 						},
 					}}},
 			},
@@ -646,8 +698,8 @@ func TestRatchetingFunctionality(t *testing.T) {
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"restricted": map[string]interface{}{
-								"minProperties": 1,
-								"maxProperties": 1,
+								"minProperties": int64(1),
+								"maxProperties": int64(1),
 							},
 						},
 					}},
@@ -679,7 +731,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"restricted": map[string]interface{}{
-								"minProperties": 2,
+								"minProperties": int64(2),
 								"maxProperties": nil,
 							},
 						},
@@ -709,7 +761,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"properties": map[string]interface{}{
 							"restricted": map[string]interface{}{
 								"minProperties": nil,
-								"maxProperties": 1,
+								"maxProperties": int64(1),
 							},
 						},
 					}},
@@ -763,7 +815,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"array": map[string]interface{}{
-								"minItems": 10,
+								"minItems": int64(10),
 							},
 						},
 					}},
@@ -825,7 +877,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"array": map[string]interface{}{
-								"maxItems": 1,
+								"maxItems": int64(1),
 							},
 						},
 					}},
@@ -884,10 +936,10 @@ func TestRatchetingFunctionality(t *testing.T) {
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"minField": map[string]interface{}{
-								"minLength": 10,
+								"minLength": int64(10),
 							},
 							"maxField": map[string]interface{}{
-								"maxLength": 15,
+								"maxLength": int64(15),
 							},
 						},
 					}},
@@ -1084,17 +1136,17 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": []interface{}{
 							map[string]interface{}{
 								"name":  "nginx",
-								"port":  443,
+								"port":  int64(443),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "etcd",
-								"port":  2379,
+								"port":  int64(2379),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "kube-apiserver",
-								"port":  6443,
+								"port":  int64(6443),
 								"field": "value",
 							},
 						},
@@ -1104,7 +1156,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 					map[string]interface{}{
 						"properties": map[string]interface{}{
 							"field": map[string]interface{}{
-								"maxItems": 2,
+								"maxItems": int64(2),
 							},
 						},
 					}},
@@ -1114,17 +1166,17 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": []interface{}{
 							map[string]interface{}{
 								"name":  "kube-apiserver",
-								"port":  6443,
+								"port":  int64(6443),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "nginx",
-								"port":  443,
+								"port":  int64(443),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "etcd",
-								"port":  2379,
+								"port":  int64(2379),
 								"field": "value",
 							},
 						},
@@ -1136,22 +1188,22 @@ func TestRatchetingFunctionality(t *testing.T) {
 							"field": []interface{}{
 								map[string]interface{}{
 									"name":  "kube-apiserver",
-									"port":  6443,
+									"port":  int64(6443),
 									"field": "value",
 								},
 								map[string]interface{}{
 									"name":  "nginx",
-									"port":  443,
+									"port":  int64(443),
 									"field": "value",
 								},
 								map[string]interface{}{
 									"name":  "etcd",
-									"port":  2379,
+									"port":  int64(2379),
 									"field": "value",
 								},
 								map[string]interface{}{
 									"name":  "dev",
-									"port":  8080,
+									"port":  int64(8080),
 									"field": "value",
 								},
 							},
@@ -1165,7 +1217,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 								"items": map[string]interface{}{
 									"properties": map[string]interface{}{
 										"port": map[string]interface{}{
-											"multipleOf": 2,
+											"multipleOf": int64(2),
 										},
 									},
 								},
@@ -1179,17 +1231,17 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": []interface{}{
 							map[string]interface{}{
 								"name":  "nginx",
-								"port":  443,
+								"port":  int64(443),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "etcd",
-								"port":  2379,
+								"port":  int64(2379),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "kube-apiserver",
-								"port":  6443,
+								"port":  int64(6443),
 								"field": "value",
 							},
 						},
@@ -1201,22 +1253,22 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": []interface{}{
 							map[string]interface{}{
 								"name":  "nginx",
-								"port":  443,
+								"port":  int64(443),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "etcd",
-								"port":  2379,
+								"port":  int64(2379),
 								"field": "value",
 							},
 							map[string]interface{}{
 								"name":  "kube-apiserver",
-								"port":  6443,
+								"port":  int64(6443),
 								"field": "this is a changed value for an an invalid but grandfathered key",
 							},
 							map[string]interface{}{
 								"name":  "dev",
-								"port":  8080,
+								"port":  int64(8080),
 								"field": "value",
 							},
 						},
@@ -1259,7 +1311,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 							"values": map[string]interface{}{
 								"items": map[string]interface{}{
 									"additionalProperties": map[string]interface{}{
-										"minLength": 6,
+										"minLength": int64(6),
 									},
 								},
 							},
@@ -1330,7 +1382,8 @@ func TestRatchetingFunctionality(t *testing.T) {
 			},
 		},
 		{
-			Name: "CEL Optional OldSelf",
+			Name:       "CEL Optional OldSelf",
+			SkipStatus: true, // oldSelf can never be null for a status update.
 			Operations: []ratchetingTestOperation{
 				updateMyCRDV1Beta1Schema{&apiextensionsv1.JSONSchemaProps{
 					Type: "object",
@@ -1461,15 +1514,15 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": map[string]interface{}{
 							"object1": map[string]interface{}{
 								"stringField": "a string",
-								"intField":    5,
+								"intField":    int64(5),
 							},
 							"object2": map[string]interface{}{
 								"stringField": "another string",
-								"intField":    15,
+								"intField":    int64(15),
 							},
 							"object3": map[string]interface{}{
 								"stringField": "a third string",
-								"intField":    7,
+								"intField":    int64(7),
 							},
 						},
 					}},
@@ -1499,19 +1552,19 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": map[string]interface{}{
 							"object1": map[string]interface{}{
 								"stringField": "a string",
-								"intField":    5,
+								"intField":    int64(5),
 							},
 							"object2": map[string]interface{}{
 								"stringField": "another string",
-								"intField":    15,
+								"intField":    int64(15),
 							},
 							"object3": map[string]interface{}{
 								"stringField": "a third string",
-								"intField":    7,
+								"intField":    int64(7),
 							},
 							"object4": map[string]interface{}{
 								"stringField": "k8s third string",
-								"intField":    7,
+								"intField":    int64(7),
 							},
 						},
 					}},
@@ -1521,12 +1574,12 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": map[string]interface{}{
 							"object1": map[string]interface{}{
 								"stringField": "a string",
-								"intField":    15,
+								"intField":    int64(15),
 							},
 							"object2": map[string]interface{}{
 								"stringField":   "another string",
-								"intField":      10,
-								"otherIntField": 20,
+								"intField":      int64(10),
+								"otherIntField": int64(20),
 							},
 						},
 					}},
@@ -1571,11 +1624,11 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": map[string]interface{}{
 							"object1": map[string]interface{}{
 								"stringField": "a string", // invalid. even number length, no k8s prefix
-								"intField":    1000,
+								"intField":    int64(1000),
 							},
 							"object4": map[string]interface{}{
 								"stringField": "k8s third string", // invalid. even number length. ratcheted
-								"intField":    7000,
+								"intField":    int64(7000),
 							},
 						},
 					}},
@@ -1586,11 +1639,11 @@ func TestRatchetingFunctionality(t *testing.T) {
 							"field": map[string]interface{}{
 								"object1": map[string]interface{}{
 									"stringField": "k8s third string",
-									"intField":    1000,
+									"intField":    int64(1000),
 								},
 								"object4": map[string]interface{}{
 									"stringField": "a string",
-									"intField":    7000,
+									"intField":    int64(7000),
 								},
 							},
 						}}},
@@ -1600,11 +1653,11 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"field": map[string]interface{}{
 							"object1": map[string]interface{}{
 								"stringField": "k8s a stringy",
-								"intField":    1000,
+								"intField":    int64(1000),
 							},
 							"object4": map[string]interface{}{
 								"stringField": "k8s third stringy",
-								"intField":    7000,
+								"intField":    int64(7000),
 							},
 						},
 					}},
@@ -1643,7 +1696,7 @@ func TestRatchetingFunctionality(t *testing.T) {
 					"reate a list of numbers with duplicates using the old simple schema",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 						"values": map[string]interface{}{
-							"dups": []interface{}{1, 2, 2, 3, 1000, 2000},
+							"dups": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
 						},
 					}},
 				patchMyCRDV1Beta1Schema{
@@ -1662,15 +1715,15 @@ func TestRatchetingFunctionality(t *testing.T) {
 						"change original without removing duplicates",
 						myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 							"values": map[string]interface{}{
-								"dups": []interface{}{1, 2, 2, 3, 1000, 2000, 3},
+								"dups": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000), int64(3)},
 							},
 						}}},
 				expectError{applyPatchOperation{
 					"add another list with duplicates",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 						"values": map[string]interface{}{
-							"dups":  []interface{}{1, 2, 2, 3, 1000, 2000},
-							"dups2": []interface{}{1, 2, 2, 3, 1000, 2000},
+							"dups":  []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
+							"dups2": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
 						},
 					}}},
 				// Can add a valid sibling field
@@ -1680,8 +1733,8 @@ func TestRatchetingFunctionality(t *testing.T) {
 					"add a valid sibling field",
 					myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
 						"values": map[string]interface{}{
-							"dups":       []interface{}{1, 2, 2, 3, 1000, 2000},
-							"otherField": []interface{}{1, 2, 3},
+							"dups":       []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
+							"otherField": []interface{}{int64(1), int64(2), int64(3)},
 						},
 					}},
 				// Can remove dups to make valid
@@ -1694,8 +1747,8 @@ func TestRatchetingFunctionality(t *testing.T) {
 					myCRDInstanceName,
 					map[string]interface{}{
 						"values": map[string]interface{}{
-							"dups":       []interface{}{1, 3, 1000, 2000},
-							"otherField": []interface{}{1, 2, 3},
+							"dups":       []interface{}{int64(1), int64(3), int64(1000), int64(2000)},
+							"otherField": []interface{}{int64(1), int64(2), int64(3)},
 						},
 					}},
 			},