Repository: knative/pkg
Pull request #2246 (2021-08-30 20:27:04)

Added invalid value field error with details
<!-- Thanks for sending a pull request! -->

# Changes

<!-- 
Describe your changes here- ideally you can get that description straight from
your descriptive commit message(s)! 

- :gift: Add new feature
- :bug: Fix bug
- :broom: Update or clean up current behavior
- :wastebasket: Remove feature or internal logic
-->

- Added new error type (Invalid Value Error with Details)
- Needed for https://github.com/knative/serving/pull/11883


<!--
In addition, categorize the changes you're making using the "/kind" Prow command, example:

/kind enhancement

Supported kinds are: api-change, bug, cleanup, deprecation, removal, documentation, enhancement, performance

-->
/kind enhancement



knative-prow-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2246#" title="Author self-approved">senthilnathan</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **vaikas** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @vaikas` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

<details open>
Needs approval from an approver in each of these files:

- **[apis/OWNERS](https://github.com/knative/pkg/blob/main/apis/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["vaikas"]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) Report
> Merging [#2246](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (6f169fa) into [main](https://codecov.io/gh/knative/pkg/commit/4472e04552d35a259e4f3efb1a3f374cb33b6b2d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (4472e04) will **decrease** coverage by `0.06%`.
> The diff coverage is `40.00%`.

> :exclamation: Current head 6f169fa differs from pull request most recent head dabb207. Consider uploading reports for the commit dabb207 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/knative/pkg/pull/2246/graphs/tree.svg?width=650&height=150&src=pr&token=rlqpDnfxbO&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)

```diff
@@            Coverage Diff             @@
##             main    #2246      +/-   ##
==========================================
- Coverage   63.99%   63.93%   -0.07%     
==========================================
  Files         220      220              
  Lines        9544     9554      +10     
==========================================
  Hits         6108     6108              
- Misses       3164     3172       +8     
- Partials      272      274       +2     
```


| [Impacted Files](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) | Coverage Δ | |
|---|---|---|
| [codegen/cmd/injection-gen/generators/client.go](https://codecov.io/gh/knative/pkg/pull/2246/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-Y29kZWdlbi9jbWQvaW5qZWN0aW9uLWdlbi9nZW5lcmF0b3JzL2NsaWVudC5nbw==) | `0.93% <0.00%> (-0.05%)` | :arrow_down: |
| [apis/field\_error.go](https://codecov.io/gh/knative/pkg/pull/2246/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-YXBpcy9maWVsZF9lcnJvci5nbw==) | `98.34% <100.00%> (+0.05%)` | :arrow_up: |
| [metrics/metrics.go](https://codecov.io/gh/knative/pkg/pull/2246/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-bWV0cmljcy9tZXRyaWNzLmdv) | `81.48% <100.00%> (ø)` | |
| [test/gcs/mock/mock.go](https://codecov.io/gh/knative/pkg/pull/2246/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-dGVzdC9nY3MvbW9jay9tb2NrLmdv) | `90.32% <0.00%> (-2.16%)` | :arrow_down: |
| [controller/controller.go](https://codecov.io/gh/knative/pkg/pull/2246/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-Y29udHJvbGxlci9jb250cm9sbGVyLmdv) | `86.49% <0.00%> (+0.62%)` | :arrow_up: |
| [resolver/addressable\_resolver.go](https://codecov.io/gh/knative/pkg/pull/2246/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-cmVzb2x2ZXIvYWRkcmVzc2FibGVfcmVzb2x2ZXIuZ28=) | `95.55% <0.00%> (+1.05%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Last update [2bd91f7...dabb207](https://codecov.io/gh/knative/pkg/pull/2246?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).


Patch:
diff --git a/.github/workflows/knative-downstream.yaml b/.github/workflows/knative-downstream.yaml
index 45d159aee..a1f5783f7 100644
--- a/.github/workflows/knative-downstream.yaml
+++ b/.github/workflows/knative-downstream.yaml
@@ -60,6 +60,8 @@ jobs:
             org: knative-sandbox
           - repo: eventing-rabbitmq
             org: knative-sandbox
+          - repo: eventing-redis
+            org: knative-sandbox
           - repo: kn-plugin-admin
             org: knative-sandbox
           - repo: net-certmanager
diff --git a/.github/workflows/tekton-downstream.yaml b/.github/workflows/tekton-downstream.yaml
index 5a7fb689a..27504416c 100644
--- a/.github/workflows/tekton-downstream.yaml
+++ b/.github/workflows/tekton-downstream.yaml
@@ -16,7 +16,7 @@ name: Downstream Tekton
 
 on:
   pull_request:
-    branches: [ 'main', 'master', 'release-*' ]
+    branches: [ 'main' ]
 
 jobs:
 
@@ -31,6 +31,8 @@ jobs:
             org: tektoncd
           - repo: chains
             org: tektoncd
+          - repo: triggers
+            org: tektoncd
 
     runs-on: ubuntu-latest
 
diff --git a/apis/duck/v1/podspec_defaults.go b/apis/duck/v1/podspec_defaults.go
new file mode 100644
index 000000000..1c6acb1e7
--- /dev/null
+++ b/apis/duck/v1/podspec_defaults.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+	"context"
+)
+
+// PodSpecDefaulter is a callback to validate a PodSpecable.
+type PodSpecDefaulter func(context.Context, *WithPod)
+
+// SetDefaults implements apis.Defaultable
+func (wp *WithPod) SetDefaults(ctx context.Context) {
+	if psd := GetPodSpecDefaulter(ctx); psd != nil {
+		psd(ctx, wp)
+	}
+}
+
+// psdKey is used for associating a PodSpecDefaulter with a context.Context
+type psdKey struct{}
+
+func WithPodSpecDefaulter(ctx context.Context, psd PodSpecDefaulter) context.Context {
+	return context.WithValue(ctx, psdKey{}, psd)
+}
+
+// GetPodSpecDefaulter extracts the PodSpecDefaulter from the context.
+func GetPodSpecDefaulter(ctx context.Context) PodSpecDefaulter {
+	untyped := ctx.Value(psdKey{})
+	if untyped == nil {
+		return nil
+	}
+	return untyped.(PodSpecDefaulter)
+}
+
+// PodDefaulter is a callback to validate a Pod.
+type PodDefaulter func(context.Context, *Pod)
+
+// SetDefaults implements apis.Defaultable
+func (p *Pod) SetDefaults(ctx context.Context) {
+	if pd := GetPodDefaulter(ctx); pd != nil {
+		pd(ctx, p)
+	}
+}
+
+// pdKey is used for associating a PodDefaulter with a context.Context
+type pdKey struct{}
+
+func WithPodDefaulter(ctx context.Context, pd PodDefaulter) context.Context {
+	return context.WithValue(ctx, pdKey{}, pd)
+}
+
+// GetPodDefaulter extracts the PodDefaulter from the context.
+func GetPodDefaulter(ctx context.Context) PodDefaulter {
+	untyped := ctx.Value(pdKey{})
+	if untyped == nil {
+		return nil
+	}
+	return untyped.(PodDefaulter)
+}
diff --git a/apis/duck/v1/podspec_types.go b/apis/duck/v1/podspec_types.go
index a8d172416..7b1946e50 100644
--- a/apis/duck/v1/podspec_types.go
+++ b/apis/duck/v1/podspec_types.go
@@ -52,18 +52,20 @@ type WithPodSpec struct {
 
 // Verify WithPod resources meet duck contracts.
 var (
+	_ apis.Validatable      = (*WithPod)(nil)
+	_ apis.Defaultable      = (*WithPod)(nil)
 	_ apis.Listable         = (*WithPod)(nil)
 	_ ducktypes.Populatable = (*WithPod)(nil)
 )
 
 // GetFullType implements duck.Implementable
-func (*PodSpecable) GetFullType() ducktypes.Populatable {
+func (wp *PodSpecable) GetFullType() ducktypes.Populatable {
 	return &WithPod{}
 }
 
 // Populate implements duck.Populatable
-func (t *WithPod) Populate() {
-	t.Spec.Template = PodSpecable{
+func (wp *WithPod) Populate() {
+	wp.Spec.Template = PodSpecable{
 		ObjectMeta: metav1.ObjectMeta{
 			Labels: map[string]string{
 				"foo": "bar",
@@ -79,7 +81,7 @@ func (t *WithPod) Populate() {
 }
 
 // GetListType implements apis.Listable
-func (*WithPod) GetListType() runtime.Object {
+func (wp *WithPod) GetListType() runtime.Object {
 	return &WithPodList{}
 }
 
@@ -92,3 +94,52 @@ type WithPodList struct {
 
 	Items []WithPod `json:"items"`
 }
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// Pod is a wrapper around Pod-like resource, which supports our interfaces
+// for webhooks
+type Pod struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec corev1.PodSpec `json:"spec,omitempty"`
+}
+
+// Verify Pod resources meet duck contracts.
+var (
+	_ apis.Validatable      = (*Pod)(nil)
+	_ apis.Defaultable      = (*Pod)(nil)
+	_ apis.Listable         = (*Pod)(nil)
+	_ ducktypes.Populatable = (*Pod)(nil)
+)
+
+// GetFullType implements duck.Implementable
+func (p *Pod) GetFullType() ducktypes.Populatable {
+	return &Pod{}
+}
+
+// Populate implements duck.Populatable
+func (p *Pod) Populate() {
+	p.Spec = corev1.PodSpec{
+		Containers: []corev1.Container{{
+			Name:  "container-name",
+			Image: "container-image:latest",
+		}},
+	}
+}
+
+// GetListType implements apis.Listable
+func (p *Pod) GetListType() runtime.Object {
+	return &PodList{}
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// PodList is a list of WithPod resources
+type PodList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+
+	Items []WithPod `json:"items"`
+}
diff --git a/apis/duck/v1/podspec_validation.go b/apis/duck/v1/podspec_validation.go
new file mode 100644
index 000000000..1ec6ef48d
--- /dev/null
+++ b/apis/duck/v1/podspec_validation.go
@@ -0,0 +1,77 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+	"context"
+
+	"knative.dev/pkg/apis"
+)
+
+// PodSpecValidator is a callback to validate a PodSpecable.
+type PodSpecValidator func(context.Context, *WithPod) *apis.FieldError
+
+// Validate implements apis.Validatable
+func (wp *WithPod) Validate(ctx context.Context) *apis.FieldError {
+	if psv := GetPodSpecValidator(ctx); psv != nil {
+		return psv(ctx, wp)
+	}
+	return nil
+}
+
+// psvKey is used for associating a PodSpecValidator with a context.Context
+type psvKey struct{}
+
+func WithPodSpecValidator(ctx context.Context, psv PodSpecValidator) context.Context {
+	return context.WithValue(ctx, psvKey{}, psv)
+}
+
+// GetPodSpecValidator extracts the PodSpecValidator from the context.
+func GetPodSpecValidator(ctx context.Context) PodSpecValidator {
+	untyped := ctx.Value(psvKey{})
+	if untyped == nil {
+		return nil
+	}
+	return untyped.(PodSpecValidator)
+}
+
+// PodValidator is a callback to validate Pods.
+type PodValidator func(context.Context, *Pod) *apis.FieldError
+
+// Validate implements apis.Validatable
+func (p *Pod) Validate(ctx context.Context) *apis.FieldError {
+	if pv := GetPodValidator(ctx); pv != nil {
+		return pv(ctx, p)
+	}
+	return nil
+}
+
+// pvKey is used for associating a PodValidator with a context.Context
+type pvKey struct{}
+
+func WithPodValidator(ctx context.Context, pv PodValidator) context.Context {
+	return context.WithValue(ctx, pvKey{}, pv)
+}
+
+// GetPodValidator extracts the PodValidator from the context.
+func GetPodValidator(ctx context.Context) PodValidator {
+	untyped := ctx.Value(pvKey{})
+	if untyped == nil {
+		return nil
+	}
+	return untyped.(PodValidator)
+}
diff --git a/apis/duck/v1/zz_generated.deepcopy.go b/apis/duck/v1/zz_generated.deepcopy.go
index ae98f0468..22088b214 100644
--- a/apis/duck/v1/zz_generated.deepcopy.go
+++ b/apis/duck/v1/zz_generated.deepcopy.go
@@ -367,6 +367,66 @@ func (in *KResourceList) DeepCopyObject() runtime.Object {
 	return nil
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Pod) DeepCopyInto(out *Pod) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pod.
+func (in *Pod) DeepCopy() *Pod {
+	if in == nil {
+		return nil
+	}
+	out := new(Pod)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Pod) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PodList) DeepCopyInto(out *PodList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]WithPod, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodList.
+func (in *PodList) DeepCopy() *PodList {
+	if in == nil {
+		return nil
+	}
+	out := new(PodList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PodList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PodSpecable) DeepCopyInto(out *PodSpecable) {
 	*out = *in
diff --git a/apis/field_error.go b/apis/field_error.go
index 4a5242851..5ea51c0e4 100644
--- a/apis/field_error.go
+++ b/apis/field_error.go
@@ -323,12 +323,13 @@ func ErrInvalidArrayValue(value interface{}, field string, index int) *FieldErro
 	return ErrInvalidValue(value, CurrentField).ViaFieldIndex(field, index)
 }
 
-// ErrInvalidValue constructs a FieldError for a field that has received an
-// invalid value.
-func ErrInvalidValue(value interface{}, fieldPath string) *FieldError {
+// ErrInvalidValue is a variadic helper method for constructing a FieldError
+// for a field that has received an invalid value.
+func ErrInvalidValue(value interface{}, fieldPath string, details ...string) *FieldError {
 	return &FieldError{
 		Message: fmt.Sprint("invalid value: ", value),
 		Paths:   []string{fieldPath},
+		Details: strings.Join(details, ", "),
 	}
 }
 
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go
index 9779d3ff3..cc0f009fd 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go
@@ -73,20 +73,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1.CustomResourceDefinition) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1.CustomResourceDefinition if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1.CustomResourceDefinition.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1.CustomResourceDefinition) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1.CustomResourceDefinition) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1.CustomResourceDefinition resources.
@@ -257,10 +243,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/state.go b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/state.go
index 369daca75..9239012d8 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/state.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1.CustomResourceDefinition) (string, doRe
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go
index 0c1626725..279dc7568 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go
@@ -73,20 +73,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1beta1.CustomResourceDefinition) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1beta1.CustomResourceDefinition if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.CustomResourceDefinition.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1beta1.CustomResourceDefinition) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1beta1.CustomResourceDefinition) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1beta1.CustomResourceDefinition resources.
@@ -257,10 +243,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/state.go b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/state.go
index 5ca5a5bb4..0a5f7b95e 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/state.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1beta1.CustomResourceDefinition) (string,
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go b/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go
index 0ef0c7c95..b231ca8da 100644
--- a/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go
+++ b/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go
@@ -73,20 +73,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1.Deployment) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1.Deployment if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1.Deployment.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1.Deployment) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1.Deployment) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1.Deployment resources.
@@ -257,10 +243,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/kube/reconciler/apps/v1/deployment/state.go b/client/injection/kube/reconciler/apps/v1/deployment/state.go
index 4d98e0625..38758d0b7 100644
--- a/client/injection/kube/reconciler/apps/v1/deployment/state.go
+++ b/client/injection/kube/reconciler/apps/v1/deployment/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1.Deployment) (string, doReconcile) {
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/kube/reconciler/apps/v1beta1/deployment/reconciler.go b/client/injection/kube/reconciler/apps/v1beta1/deployment/reconciler.go
index 085243570..b6b8ca182 100644
--- a/client/injection/kube/reconciler/apps/v1beta1/deployment/reconciler.go
+++ b/client/injection/kube/reconciler/apps/v1beta1/deployment/reconciler.go
@@ -73,20 +73,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1beta1.Deployment) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1beta1.Deployment if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Deployment.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1beta1.Deployment) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1beta1.Deployment) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1beta1.Deployment resources.
@@ -257,10 +243,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/kube/reconciler/apps/v1beta1/deployment/state.go b/client/injection/kube/reconciler/apps/v1beta1/deployment/state.go
index addc5a2f1..96e18f97a 100644
--- a/client/injection/kube/reconciler/apps/v1beta1/deployment/state.go
+++ b/client/injection/kube/reconciler/apps/v1beta1/deployment/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1beta1.Deployment) (string, doReconcile)
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/kube/reconciler/apps/v1beta2/deployment/reconciler.go b/client/injection/kube/reconciler/apps/v1beta2/deployment/reconciler.go
index b8a26aafc..88938ab8e 100644
--- a/client/injection/kube/reconciler/apps/v1beta2/deployment/reconciler.go
+++ b/client/injection/kube/reconciler/apps/v1beta2/deployment/reconciler.go
@@ -73,20 +73,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1beta2.Deployment) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1beta2.Deployment if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1beta2.Deployment.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1beta2.Deployment) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1beta2.Deployment) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1beta2.Deployment resources.
@@ -257,10 +243,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/kube/reconciler/apps/v1beta2/deployment/state.go b/client/injection/kube/reconciler/apps/v1beta2/deployment/state.go
index d3e701d73..b52b324d2 100644
--- a/client/injection/kube/reconciler/apps/v1beta2/deployment/state.go
+++ b/client/injection/kube/reconciler/apps/v1beta2/deployment/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1beta2.Deployment) (string, doReconcile)
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/kube/reconciler/core/v1/namespace/reconciler.go b/client/injection/kube/reconciler/core/v1/namespace/reconciler.go
index f1211c26b..00747a699 100644
--- a/client/injection/kube/reconciler/core/v1/namespace/reconciler.go
+++ b/client/injection/kube/reconciler/core/v1/namespace/reconciler.go
@@ -72,20 +72,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1.Namespace) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1.Namespace if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1.Namespace.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1.Namespace) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1.Namespace) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1.Namespace resources.
@@ -256,10 +242,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/kube/reconciler/core/v1/namespace/state.go b/client/injection/kube/reconciler/core/v1/namespace/state.go
index eb164d5e7..de1509ee9 100644
--- a/client/injection/kube/reconciler/core/v1/namespace/state.go
+++ b/client/injection/kube/reconciler/core/v1/namespace/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1.Namespace) (string, doReconcile) {
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/kube/reconciler/core/v1/secret/reconciler.go b/client/injection/kube/reconciler/core/v1/secret/reconciler.go
index fad9f24db..97ebca4fb 100644
--- a/client/injection/kube/reconciler/core/v1/secret/reconciler.go
+++ b/client/injection/kube/reconciler/core/v1/secret/reconciler.go
@@ -70,20 +70,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1.Secret) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1.Secret if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1.Secret.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1.Secret) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1.Secret) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1.Secret resources.
@@ -247,10 +233,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Report the reconciler event, if any.
diff --git a/client/injection/kube/reconciler/core/v1/secret/state.go b/client/injection/kube/reconciler/core/v1/secret/state.go
index 118b3ebdc..5bf837037 100644
--- a/client/injection/kube/reconciler/core/v1/secret/state.go
+++ b/client/injection/kube/reconciler/core/v1/secret/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1.Secret) (string, doReconcile) {
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/client/injection/kube/reconciler/extensions/v1beta1/deployment/reconciler.go b/client/injection/kube/reconciler/extensions/v1beta1/deployment/reconciler.go
index 0def7abba..0c306632a 100644
--- a/client/injection/kube/reconciler/extensions/v1beta1/deployment/reconciler.go
+++ b/client/injection/kube/reconciler/extensions/v1beta1/deployment/reconciler.go
@@ -73,20 +73,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx context.Context, o *v1beta1.Deployment) reconciler.Event
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing v1beta1.Deployment if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Deployment.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx context.Context, o *v1beta1.Deployment) reconciler.Event
-}
-
 type doReconcile func(ctx context.Context, o *v1beta1.Deployment) reconciler.Event
 
 // reconcilerImpl implements controller.Reconciler for v1beta1.Deployment resources.
@@ -257,10 +243,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			return fmt.Errorf("failed to clear finalizers: %w", err)
 		}
 
-	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	// Synchronize the status.
diff --git a/client/injection/kube/reconciler/extensions/v1beta1/deployment/state.go b/client/injection/kube/reconciler/extensions/v1beta1/deployment/state.go
index 47c279b6a..c51492d21 100644
--- a/client/injection/kube/reconciler/extensions/v1beta1/deployment/state.go
+++ b/client/injection/kube/reconciler/extensions/v1beta1/deployment/state.go
@@ -41,10 +41,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -57,7 +53,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor(types.NamespacedName{
 		Namespace: namespace,
@@ -71,8 +66,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -82,8 +75,8 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -99,8 +92,6 @@ func (s *state) reconcileMethodFor(o *v1beta1.Deployment) (string, doReconcile)
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return reconciler.DoFinalizeKind, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/codegen/cmd/injection-gen/generators/reconciler_reconciler.go b/codegen/cmd/injection-gen/generators/reconciler_reconciler.go
index 1781a60e3..96e1edc25 100644
--- a/codegen/cmd/injection-gen/generators/reconciler_reconciler.go
+++ b/codegen/cmd/injection-gen/generators/reconciler_reconciler.go
@@ -206,10 +206,6 @@ func (g *reconcilerReconcilerGenerator) GenerateType(c *generator.Context, t *ty
 			Package: "knative.dev/pkg/reconciler",
 			Name:    "DoFinalizeKind",
 		}),
-		"doObserveFinalizeKind": c.Universe.Type(types.Name{
-			Package: "knative.dev/pkg/reconciler",
-			Name:    "DoObserveFinalizeKind",
-		}),
 		"controllerIsSkipKey": c.Universe.Function(types.Name{
 			Package: "knative.dev/pkg/controller",
 			Name:    "IsSkipKey",
@@ -264,20 +260,6 @@ type ReadOnlyInterface interface {
 	ObserveKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
 }
 
-// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
-// controller finalizing {{.type|raw}} if they want to process tombstoned resources
-// even when they are not the leader.  Due to the nature of how finalizers are handled
-// there are no guarantees that this will be called.
-//
-// Deprecated: Use reconciler.OnDeletionInterface instead.
-type ReadOnlyFinalizer interface {
-	// ObserveFinalizeKind implements custom logic to observe the final state of {{.type|raw}}.
-	// This method should not write to the API.
-	//
-	// Deprecated: Use reconciler.ObserveDeletion instead.
-	ObserveFinalizeKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
-}
-
 type doReconcile func(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
 
 // reconcilerImpl implements controller.Reconciler for {{.type|raw}} resources.
@@ -483,10 +465,6 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro
 			return {{.fmtErrorf|raw}}("failed to clear finalizers: %w", err)
 		}
 
-	case {{.doObserveKind|raw}}, {{.doObserveFinalizeKind|raw}}:
-		// Observe any changes to this resource, since we are not the leader.
-		reconcileEvent = do(ctx, resource)
-
 	}
 
 	{{if .hasStatus}}
diff --git a/codegen/cmd/injection-gen/generators/reconciler_state.go b/codegen/cmd/injection-gen/generators/reconciler_state.go
index db8334b40..9ef362505 100644
--- a/codegen/cmd/injection-gen/generators/reconciler_state.go
+++ b/codegen/cmd/injection-gen/generators/reconciler_state.go
@@ -84,10 +84,6 @@ func (g *reconcilerStateGenerator) GenerateType(c *generator.Context, t *types.T
 			Package: "knative.dev/pkg/reconciler",
 			Name:    "DoFinalizeKind",
 		}),
-		"doObserveFinalizeKind": c.Universe.Type(types.Name{
-			Package: "knative.dev/pkg/reconciler",
-			Name:    "DoObserveFinalizeKind",
-		}),
 	}
 
 	sw.Do(reconcilerStateType, m)
@@ -111,10 +107,6 @@ type state struct {
 	roi ReadOnlyInterface
 	// isROI (Read Only Interface) the reconciler only observes reconciliation.
 	isROI bool
-	// rof is the read only finalizer cast of the reconciler.
-	rof ReadOnlyFinalizer
-	// isROF (Read Only Finalizer) the reconciler only observes finalize.
-	isROF bool
 	// isLeader the instance of the reconciler is the elected leader.
 	isLeader bool
 }
@@ -127,7 +119,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 	}
 
 	roi, isROI := r.reconciler.(ReadOnlyInterface)
-	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
 
 	isLeader := r.IsLeaderFor({{.typesNamespacedName|raw}}{
 		Namespace: namespace,
@@ -141,8 +132,6 @@ func newState(key string, r *reconcilerImpl) (*state, error) {
 		reconciler: r.reconciler,
 		roi:        roi,
 		isROI:      isROI,
-		rof:        rof,
-		isROF:      isROF,
 		isLeader:   isLeader,
 	}, nil
 }
@@ -154,8 +143,8 @@ var reconcilerStateMethods = `
 // isNotLeaderNorObserver returns true when there is no work possible for the
 // reconciler.
 func (s *state) isNotLeaderNorObserver() bool {
-	if !s.isLeader && !s.isROI && !s.isROF {
-		// If we are not the leader, and we don't implement either ReadOnly
+	if !s.isLeader && !s.isROI {
+		// If we are not the leader, and we don't implement the ReadOnly
 		// interface, then take a fast-path out.
 		return true
 	}
@@ -171,8 +160,6 @@ func (s *state) reconcileMethodFor(o *{{.type|raw}}) (string, doReconcile) {
 		}
 	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
 		return {{.doFinalizeKind|raw}}, fin.FinalizeKind
-	} else if !s.isLeader && s.isROF {
-		return {{.doObserveFinalizeKind|raw}}, s.rof.ObserveFinalizeKind
 	}
 	return "unknown", nil
 }
diff --git a/leaderelection/context.go b/leaderelection/context.go
index d30ecd462..4758ccba5 100644
--- a/leaderelection/context.go
+++ b/leaderelection/context.go
@@ -94,7 +94,11 @@ func BuildElector(ctx context.Context, la reconciler.LeaderAware, queueName stri
 	return &unopposedElector{
 		la:  la,
 		bkt: reconciler.UniversalBucket(),
-		enq: enq,
+		// The UniversalBucket owns everything, so there is never a need to
+		// enqueue things (no possible state change).  We pass nil here to
+		// avoid filling the queue for an extra resynce at startup along
+		// this path.
+		enq: nil,
 	}, nil
 }
 
diff --git a/reconciler/leader.go b/reconciler/leader.go
index 6fece8526..ed7254c46 100644
--- a/reconciler/leader.go
+++ b/reconciler/leader.go
@@ -97,7 +97,7 @@ func (laf *LeaderAwareFuncs) Promote(b Bucket, enq func(Bucket, types.Namespaced
 		laf.buckets[b.Name()] = b
 	}()
 
-	if promote := laf.PromoteFunc; promote != nil {
+	if promote := laf.PromoteFunc; promote != nil && enq != nil {
 		return promote(b, enq)
 	}
 	return nil
diff --git a/reconciler/reconcile_common.go b/reconciler/reconcile_common.go
index 114d53b03..34d56b2f4 100644
--- a/reconciler/reconcile_common.go
+++ b/reconciler/reconcile_common.go
@@ -38,6 +38,8 @@ const (
 	// DoObserveKind is the function name for observing the resource (as a non leader).
 	DoObserveKind = "ObserveKind"
 	// DoObserveFinalizeKind is the function name for observing finalization of the resource (as a non leader).
+	//
+	// Deprecated: This will be deleted soon.
 	DoObserveFinalizeKind = "ObserveFinalizeKind"
 )
 
diff --git a/webhook/json/decode.go b/webhook/json/decode.go
new file mode 100644
index 000000000..b62caec61
--- /dev/null
+++ b/webhook/json/decode.go
@@ -0,0 +1,127 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package json
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+)
+
+var (
+	emptyMeta  = []byte(`:{}`)
+	metaPrefix = []byte(`{"metadata"`)
+	metaSuffix = []byte(`}`)
+)
+
+var (
+	// Unmarshal is an alias for json.Unmarshal
+	Unmarshal = json.Unmarshal
+
+	//Marshal is an alias for json.Marshal
+	Marshal = json.Marshal
+)
+
+// Decode will parse the json byte array to the target object. When
+// unknown fields are _not_ allowed we still accept unknown
+// fields in the Object's metadata
+//
+// See https://github.com/knative/serving/issues/11448 for details
+func Decode(bites []byte, target interface{}, disallowUnknownFields bool) error {
+	if !disallowUnknownFields {
+		return json.Unmarshal(bites, target)
+	}
+
+	// If we don't allow unknown fields we skip validating fields in the metadata
+	// block since that is opaque to us and validated by the API server
+	start, end, err := findMetadataOffsets(bites)
+	if err != nil {
+		return err
+	} else if start == -1 || end == -1 {
+		// If for some reason the json does not have metadata continue with normal parsing
+		dec := json.NewDecoder(bytes.NewReader(bites))
+		dec.DisallowUnknownFields()
+		return dec.Decode(target)
+	}
+
+	before := bites[:start]
+	metadata := bites[start:end]
+	after := bites[end:]
+
+	// Parse everything but skip metadata
+	dec := json.NewDecoder(io.MultiReader(
+		bytes.NewReader(before),
+		bytes.NewReader(emptyMeta),
+		bytes.NewReader(after),
+	))
+
+	dec.DisallowUnknownFields()
+	if err := dec.Decode(target); err != nil {
+		return err
+	}
+
+	// Now we parse just the metadata
+	dec = json.NewDecoder(io.MultiReader(
+		bytes.NewReader(metaPrefix),
+		bytes.NewReader(metadata),
+		bytes.NewReader(metaSuffix),
+	))
+
+	return dec.Decode(target)
+}
+
+func findMetadataOffsets(bites []byte) (start, end int64, err error) {
+	start, end = -1, -1
+	level := 0
+
+	var (
+		dec = json.NewDecoder(bytes.NewReader(bites))
+		t   json.Token
+	)
+
+	for {
+		t, err = dec.Token()
+		if err == io.EOF { //nolint
+			break
+		}
+		if err != nil {
+			return
+		}
+
+		switch v := t.(type) {
+		case json.Delim:
+			if v == '{' {
+				level++
+			} else if v == '}' {
+				level--
+			}
+		case string:
+			if v == "metadata" && level == 1 {
+				start = dec.InputOffset()
+				x := struct{}{}
+				if err = dec.Decode(&x); err != nil {
+					return -1, -1, err
+				}
+				end = dec.InputOffset()
+
+				// we exit early to stop processing the rest of the object
+				return
+			}
+		}
+	}
+	return -1, -1, nil
+}
diff --git a/webhook/resourcesemantics/defaulting/defaulting.go b/webhook/resourcesemantics/defaulting/defaulting.go
index a5bcc4137..504dcaa06 100644
--- a/webhook/resourcesemantics/defaulting/defaulting.go
+++ b/webhook/resourcesemantics/defaulting/defaulting.go
@@ -17,9 +17,7 @@ limitations under the License.
 package defaulting
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"sort"
@@ -47,6 +45,7 @@ import (
 	"knative.dev/pkg/system"
 	"knative.dev/pkg/webhook"
 	certresources "knative.dev/pkg/webhook/certificates/resources"
+	"knative.dev/pkg/webhook/json"
 	"knative.dev/pkg/webhook/resourcesemantics"
 )
 
@@ -241,21 +240,15 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1.AdmissionRequ
 
 	if len(newBytes) != 0 {
 		newObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD)
-		newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
-		if ac.disallowUnknownFields {
-			newDecoder.DisallowUnknownFields()
-		}
-		if err := newDecoder.Decode(&newObj); err != nil {
+		err := json.Decode(newBytes, newObj, ac.disallowUnknownFields)
+		if err != nil {
 			return nil, fmt.Errorf("cannot decode incoming new object: %w", err)
 		}
 	}
 	if len(oldBytes) != 0 {
 		oldObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD)
-		oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
-		if ac.disallowUnknownFields {
-			oldDecoder.DisallowUnknownFields()
-		}
-		if err := oldDecoder.Decode(&oldObj); err != nil {
+		err := json.Decode(oldBytes, oldObj, ac.disallowUnknownFields)
+		if err != nil {
 			return nil, fmt.Errorf("cannot decode incoming old object: %w", err)
 		}
 	}
diff --git a/webhook/resourcesemantics/validation/validation_admit.go b/webhook/resourcesemantics/validation/validation_admit.go
index db805b4a5..0544d88a6 100644
--- a/webhook/resourcesemantics/validation/validation_admit.go
+++ b/webhook/resourcesemantics/validation/validation_admit.go
@@ -17,9 +17,7 @@ limitations under the License.
 package validation
 
 import (
-	"bytes"
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 
@@ -31,6 +29,7 @@ import (
 	kubeclient "knative.dev/pkg/client/injection/kube/client"
 	"knative.dev/pkg/logging"
 	"knative.dev/pkg/webhook"
+	"knative.dev/pkg/webhook/json"
 	"knative.dev/pkg/webhook/resourcesemantics"
 )
 
@@ -110,11 +109,8 @@ func (ac *reconciler) decodeRequestAndPrepareContext(
 	var newObj resourcesemantics.GenericCRD
 	if len(newBytes) != 0 {
 		newObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD)
-		newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
-		if ac.disallowUnknownFields {
-			newDecoder.DisallowUnknownFields()
-		}
-		if err := newDecoder.Decode(&newObj); err != nil {
+		err := json.Decode(newBytes, newObj, ac.disallowUnknownFields)
+		if err != nil {
 			return ctx, nil, fmt.Errorf("cannot decode incoming new object: %w", err)
 		}
 	}
@@ -122,11 +118,8 @@ func (ac *reconciler) decodeRequestAndPrepareContext(
 	var oldObj resourcesemantics.GenericCRD
 	if len(oldBytes) != 0 {
 		oldObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD)
-		oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
-		if ac.disallowUnknownFields {
-			oldDecoder.DisallowUnknownFields()
-		}
-		if err := oldDecoder.Decode(&oldObj); err != nil {
+		err := json.Decode(oldBytes, oldObj, ac.disallowUnknownFields)
+		if err != nil {
 			return ctx, nil, fmt.Errorf("cannot decode incoming old object: %w", err)
 		}
 	}


Test patch:
diff --git a/apis/duck/v1/implements_test.go b/apis/duck/v1/implements_test.go
index a786f41f0..f55fc57ba 100644
--- a/apis/duck/v1/implements_test.go
+++ b/apis/duck/v1/implements_test.go
@@ -21,6 +21,7 @@ import (
 
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
+	corev1 "k8s.io/api/core/v1"
 
 	"knative.dev/pkg/apis/duck"
 	"knative.dev/pkg/apis/duck/ducktypes"
@@ -33,6 +34,7 @@ func TestTypesImplements(t *testing.T) {
 	}{
 		{instance: &AddressableType{}, iface: &Addressable{}},
 		{instance: &KResource{}, iface: &Conditions{}},
+		{instance: &corev1.Pod{}, iface: &Pod{}},
 	}
 	for _, tc := range testCases {
 		if err := duck.VerifyType(tc.instance, tc.iface); err != nil {
diff --git a/apis/duck/v1/podspec_defaults_test.go b/apis/duck/v1/podspec_defaults_test.go
new file mode 100644
index 000000000..c2db357b7
--- /dev/null
+++ b/apis/duck/v1/podspec_defaults_test.go
@@ -0,0 +1,164 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+	"context"
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	corev1 "k8s.io/api/core/v1"
+)
+
+func TestPodSpecDefaulting(t *testing.T) {
+	p := WithPod{
+		Spec: WithPodSpec{
+			Template: PodSpecable{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{{
+						Name:  "blah",
+						Image: "busybox",
+					}},
+				},
+			},
+		},
+	}
+
+	tests := []struct {
+		name string
+		with func(context.Context) context.Context
+		want *WithPod
+	}{{
+		name: "no check",
+		with: func(ctx context.Context) context.Context {
+			return ctx
+		},
+		want: p.DeepCopy(),
+	}, {
+		name: "no change",
+		with: func(ctx context.Context) context.Context {
+			return WithPodSpecDefaulter(ctx, func(ctx context.Context, wp *WithPod) {
+			})
+		},
+		want: p.DeepCopy(),
+	}, {
+		name: "no busybox",
+		with: func(ctx context.Context) context.Context {
+			return WithPodSpecDefaulter(ctx, func(ctx context.Context, wp *WithPod) {
+				for i, c := range wp.Spec.Template.Spec.InitContainers {
+					if !strings.Contains(c.Image, "@") {
+						wp.Spec.Template.Spec.InitContainers[i].Image = c.Image + "@sha256:deadbeef"
+					}
+				}
+				for i, c := range wp.Spec.Template.Spec.Containers {
+					if !strings.Contains(c.Image, "@") {
+						wp.Spec.Template.Spec.Containers[i].Image = c.Image + "@sha256:deadbeef"
+					}
+				}
+			})
+		},
+		want: &WithPod{
+			Spec: WithPodSpec{
+				Template: PodSpecable{
+					Spec: corev1.PodSpec{
+						Containers: []corev1.Container{{
+							Name:  "blah",
+							Image: "busybox@sha256:deadbeef",
+						}},
+					},
+				},
+			},
+		},
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			ctx := test.with(context.Background())
+			got := p.DeepCopy()
+			got.SetDefaults(ctx)
+			if !cmp.Equal(test.want, got) {
+				t.Errorf("SetDefaults (-want, +got) = %s", cmp.Diff(test.want, got))
+			}
+		})
+	}
+}
+
+func TestPodDefaulting(t *testing.T) {
+	p := Pod{
+		Spec: corev1.PodSpec{
+			Containers: []corev1.Container{{
+				Name:  "blah",
+				Image: "busybox",
+			}},
+		},
+	}
+
+	tests := []struct {
+		name string
+		with func(context.Context) context.Context
+		want *Pod
+	}{{
+		name: "no check",
+		with: func(ctx context.Context) context.Context {
+			return ctx
+		},
+		want: p.DeepCopy(),
+	}, {
+		name: "no change",
+		with: func(ctx context.Context) context.Context {
+			return WithPodDefaulter(ctx, func(ctx context.Context, wp *Pod) {
+			})
+		},
+		want: p.DeepCopy(),
+	}, {
+		name: "no busybox",
+		with: func(ctx context.Context) context.Context {
+			return WithPodDefaulter(ctx, func(ctx context.Context, wp *Pod) {
+				for i, c := range wp.Spec.InitContainers {
+					if !strings.Contains(c.Image, "@") {
+						wp.Spec.InitContainers[i].Image = c.Image + "@sha256:deadbeef"
+					}
+				}
+				for i, c := range wp.Spec.Containers {
+					if !strings.Contains(c.Image, "@") {
+						wp.Spec.Containers[i].Image = c.Image + "@sha256:deadbeef"
+					}
+				}
+			})
+		},
+		want: &Pod{
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{{
+					Name:  "blah",
+					Image: "busybox@sha256:deadbeef",
+				}},
+			},
+		},
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			ctx := test.with(context.Background())
+			got := p.DeepCopy()
+			got.SetDefaults(ctx)
+			if !cmp.Equal(test.want, got) {
+				t.Errorf("SetDefaults (-want, +got) = %s", cmp.Diff(test.want, got))
+			}
+		})
+	}
+}
diff --git a/apis/duck/v1/podspec_validation_test.go b/apis/duck/v1/podspec_validation_test.go
new file mode 100644
index 000000000..41efc2738
--- /dev/null
+++ b/apis/duck/v1/podspec_validation_test.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+	"context"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	"knative.dev/pkg/apis"
+)
+
+func TestPodSpecValidation(t *testing.T) {
+	tests := []struct {
+		name string
+		with func(context.Context) context.Context
+		want *apis.FieldError
+	}{{
+		name: "no check",
+		with: func(ctx context.Context) context.Context {
+			return ctx
+		},
+		want: nil,
+	}, {
+		name: "no error",
+		with: func(ctx context.Context) context.Context {
+			return WithPodSpecValidator(ctx, func(ctx context.Context, wp *WithPod) *apis.FieldError {
+				return nil
+			})
+		},
+		want: nil,
+	}, {
+		name: "no busybox",
+		with: func(ctx context.Context) context.Context {
+			return WithPodSpecValidator(ctx, func(ctx context.Context, wp *WithPod) *apis.FieldError {
+				for i, c := range wp.Spec.Template.Spec.InitContainers {
+					if c.Image == "busybox" {
+						return apis.ErrInvalidValue(c.Image, "image").ViaFieldIndex("spec.template.spec.initContainers", i)
+					}
+				}
+				for i, c := range wp.Spec.Template.Spec.Containers {
+					if c.Image == "busybox" {
+						return apis.ErrInvalidValue(c.Image, "image").ViaFieldIndex("spec.template.spec.containers", i)
+					}
+				}
+				return nil
+			})
+		},
+		want: apis.ErrInvalidValue("busybox", "spec.template.spec.containers[0].image"),
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			p := WithPod{
+				Spec: WithPodSpec{
+					Template: PodSpecable{
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{{
+								Name:  "blah",
+								Image: "busybox",
+							}},
+						},
+					},
+				},
+			}
+			ctx := test.with(context.Background())
+			got := p.Validate(ctx)
+			if test.want.Error() != got.Error() {
+				t.Errorf("Validate() = %v, wanted %v", got, test.want)
+			}
+		})
+	}
+}
+
+func TestPodValidation(t *testing.T) {
+	tests := []struct {
+		name string
+		with func(context.Context) context.Context
+		want *apis.FieldError
+	}{{
+		name: "no check",
+		with: func(ctx context.Context) context.Context {
+			return ctx
+		},
+		want: nil,
+	}, {
+		name: "no error",
+		with: func(ctx context.Context) context.Context {
+			return WithPodValidator(ctx, func(ctx context.Context, wp *Pod) *apis.FieldError {
+				return nil
+			})
+		},
+		want: nil,
+	}, {
+		name: "no busybox",
+		with: func(ctx context.Context) context.Context {
+			return WithPodValidator(ctx, func(ctx context.Context, wp *Pod) *apis.FieldError {
+				for i, c := range wp.Spec.InitContainers {
+					if c.Image == "busybox" {
+						return apis.ErrInvalidValue(c.Image, "image").ViaFieldIndex("spec.template.spec.initContainers", i)
+					}
+				}
+				for i, c := range wp.Spec.Containers {
+					if c.Image == "busybox" {
+						return apis.ErrInvalidValue(c.Image, "image").ViaFieldIndex("spec.template.spec.containers", i)
+					}
+				}
+				return nil
+			})
+		},
+		want: apis.ErrInvalidValue("busybox", "spec.template.spec.containers[0].image"),
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			p := Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{{
+						Name:  "blah",
+						Image: "busybox",
+					}},
+				},
+			}
+			ctx := test.with(context.Background())
+			got := p.Validate(ctx)
+			if test.want.Error() != got.Error() {
+				t.Errorf("Validate() = %v, wanted %v", got, test.want)
+			}
+		})
+	}
+}
diff --git a/apis/field_error_test.go b/apis/field_error_test.go
index 2f1a78a38..20fdf03cb 100644
--- a/apis/field_error_test.go
+++ b/apis/field_error_test.go
@@ -157,6 +157,11 @@ Body.`,
 		err:      ErrInvalidValue(5*time.Second, "bar"),
 		prefixes: [][]string{{"baz"}},
 		want:     `invalid value: 5s: baz.bar`,
+	}, {
+		name:     "invalid value details propagation",
+		err:      ErrInvalidValue("foo", "bar", "qux"),
+		prefixes: [][]string{{"baz"}},
+		want:     "invalid value: foo: baz.bar\nqux",
 	}, {
 		name:     "invalid value propagation",
 		err:      ErrGeneric("this is a generic error", "foo", "bar"),
diff --git a/controller/controller_test.go b/controller/controller_test.go
index 2e9fb45b3..6ce2e3dd0 100644
--- a/controller/controller_test.go
+++ b/controller/controller_test.go
@@ -941,15 +941,15 @@ func (cr *countingLeaderAwareReconciler) Reconcile(ctx context.Context, key stri
 }
 
 func TestStartAndShutdownWithLeaderAwareNoElection(t *testing.T) {
-	promoted := make(chan struct{})
 	r := &countingLeaderAwareReconciler{
 		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
 			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
-				close(promoted)
+				t.Error("Promote should not be called when no leader election is enabled.")
 				return nil
 			},
 		},
 	}
+
 	impl := NewContext(context.TODO(), r, ControllerOptions{
 		Logger:        TestLogger(t),
 		WorkQueueName: "Testing",
@@ -968,13 +968,10 @@ func TestStartAndShutdownWithLeaderAwareNoElection(t *testing.T) {
 	})
 
 	select {
-	case <-promoted:
-		// We expect to be promoted immediately, since there is no
-		// ElectorBuilder attached to the context.
 	case <-doneCh:
 		t.Fatal("StartAll finished early.")
 	case <-time.After(10 * time.Second):
-		t.Error("Timed out waiting for StartAll.")
+		// Give it some time to run.
 	}
 
 	cancel()
diff --git a/leaderelection/context_test.go b/leaderelection/context_test.go
index 271bab215..1732e9557 100644
--- a/leaderelection/context_test.go
+++ b/leaderelection/context_test.go
@@ -323,3 +323,35 @@ func TestWithStatefulSetBuilder(t *testing.T) {
 		t.Fatal("Timed out waiting for promotion.")
 	}
 }
+
+func TestWithUnopposedElector(t *testing.T) {
+	laf := &reconciler.LeaderAwareFuncs{
+		PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
+			t.Error("Unexpected call to PromoteFunc with unopposedElector")
+			return nil
+		},
+	}
+
+	ctx := context.Background()
+	if HasLeaderElection(ctx) {
+		t.Error("HasLeaderElection() = true, wanted false")
+	}
+
+	le, err := BuildElector(ctx, laf, "name", func(reconciler.Bucket, types.NamespacedName) {
+		t.Error("Unexpected call to enqueue function.")
+	})
+	if err != nil {
+		t.Fatal("BuildElector() =", err)
+	}
+
+	if _, ok := le.(*unopposedElector); !ok {
+		t.Fatalf("BuildElector() = %T, wanted an unopposedElector", le)
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+	go le.Run(ctx)
+
+	// Wait to see if PromoteFunc is called with nil or our enq function.
+	<-time.After(time.Second)
+}
diff --git a/reconciler/leader_test.go b/reconciler/leader_test.go
index 34d6eefe5..82f32ce1a 100644
--- a/reconciler/leader_test.go
+++ b/reconciler/leader_test.go
@@ -42,6 +42,11 @@ func TestLeaderAwareFuncs(t *testing.T) {
 	}
 
 	laf.PromoteFunc = func(bkt Bucket, gotFunc func(Bucket, types.NamespacedName)) error {
+		if gotFunc == nil {
+			t.Error("PromoteFunc should not be called with nil gotFunc")
+			return nil
+		}
+
 		gotFunc(bkt, wantKey)
 		if !called {
 			t.Error("gotFunc didn't call wantFunc!")
@@ -79,4 +84,7 @@ func TestLeaderAwareFuncs(t *testing.T) {
 	if laf.IsLeaderFor(wantKey) {
 		t.Error("IsLeaderFor() = true, wanted false")
 	}
+
+	// We should elide calls with a nil enq
+	laf.Promote(wantBkt, nil)
 }
diff --git a/test/request.go b/test/request.go
index 821696f5d..fd495c121 100644
--- a/test/request.go
+++ b/test/request.go
@@ -21,7 +21,6 @@ package test
 import (
 	"context"
 	"net/url"
-	"strings"
 	"time"
 
 	"k8s.io/client-go/kubernetes"
@@ -38,11 +37,6 @@ type RequestOption = spoof.RequestOption
 // Deprecated: Use the spoof package version
 var WithHeader = spoof.WithHeader
 
-// Retrying modifies a ResponseChecker to retry certain response codes.
-//
-// Deprecated: Use the spoof package version
-var Retrying = spoof.Retrying
-
 // IsOneOfStatusCodes checks that the response code is equal to the given one.
 //
 // Deprecated: Use the spoof package version
@@ -75,15 +69,6 @@ var MatchesBody = spoof.MatchesBody
 // Deprecated: Use the spoof package version
 var MatchesAllOf = spoof.MatchesAllOf
 
-// EventuallyMatchesBody checks that the response body *eventually* matches the expected body.
-// TODO(#1178): Delete me. We don't want to need this; we should be waiting for an appropriate Status instead.
-func EventuallyMatchesBody(expected string) spoof.ResponseChecker {
-	return func(resp *spoof.Response) (bool, error) {
-		// Returning (false, nil) causes SpoofingClient.Poll to retry.
-		return strings.Contains(string(resp.Body), expected), nil
-	}
-}
-
 // WaitForEndpointState will poll an endpoint until inState indicates the state is achieved,
 // or default timeout is reached.
 // If resolvableDomain is false, it will use kubeClientset to look up the ingress and spoof
diff --git a/test/spoof/response_checks.go b/test/spoof/response_checks.go
index d5e96258f..3e22fc7cc 100644
--- a/test/spoof/response_checks.go
+++ b/test/spoof/response_checks.go
@@ -103,19 +103,3 @@ func IsOneOfStatusCodes(codes ...int) ResponseChecker {
 		return true, fmt.Errorf("status = %d %s, want one of: %v", resp.StatusCode, resp.Status, codes)
 	}
 }
-
-// Retrying modifies a ResponseChecker to retry certain response codes.
-func Retrying(rc ResponseChecker, codes ...int) ResponseChecker {
-	return func(resp *Response) (bool, error) {
-		for _, code := range codes {
-			if resp.StatusCode == code {
-				// Returning (false, nil) causes SpoofingClient.Poll to retry.
-				// sc.logger.Info("Retrying for code ", resp.StatusCode)
-				return false, nil
-			}
-		}
-
-		// If we didn't match any retryable codes, invoke the ResponseChecker that we wrapped.
-		return rc(resp)
-	}
-}
diff --git a/test/spoof/spoof.go b/test/spoof/spoof.go
index c50108c56..f2baa4edf 100644
--- a/test/spoof/spoof.go
+++ b/test/spoof/spoof.go
@@ -68,6 +68,14 @@ type ResponseChecker func(resp *Response) (done bool, err error)
 // If an error should be retried, it should return true and the wrapped error to explain why to retry.
 type ErrorRetryChecker func(e error) (retry bool, err error)
 
+// ResponseRetryChecker is used to determine if a response should be retried or not.
+// If a response should be retried, it should return true and an error to explain why to retry.
+//
+// This is distinct from ResponseChecker in that it shall be used to retry responses,
+// where the HTTP request was technically successful (it returned something) but indicates
+// an error (e.g. the overload page of a loadbalancer).
+type ResponseRetryChecker func(resp *Response) (retry bool, err error)
+
 // SpoofingClient is a minimal HTTP client wrapper that spoofs the domain of requests
 // for non-resolvable domains.
 type SpoofingClient struct {
@@ -147,16 +155,16 @@ func ResolveEndpoint(ctx context.Context, kubeClientset kubernetes.Interface, do
 // Do dispatches to the underlying http.Client.Do, spoofing domains as needed
 // and transforming the http.Response into a spoof.Response.
 // Each response is augmented with "ZipkinTraceID" header that identifies the zipkin trace corresponding to the request.
-func (sc *SpoofingClient) Do(req *http.Request, errorRetryCheckers ...ErrorRetryChecker) (*Response, error) {
+func (sc *SpoofingClient) Do(req *http.Request, errorRetryCheckers ...interface{}) (*Response, error) {
 	return sc.Poll(req, func(*Response) (bool, error) { return true, nil }, errorRetryCheckers...)
 }
 
 // Poll executes an http request until it satisfies the inState condition or, if there's an error,
 // none of the error retry checkers permit a retry.
 // If no retry checkers are specified `DefaultErrorRetryChecker` will be used.
-func (sc *SpoofingClient) Poll(req *http.Request, inState ResponseChecker, errorRetryCheckers ...ErrorRetryChecker) (*Response, error) {
-	if len(errorRetryCheckers) == 0 {
-		errorRetryCheckers = []ErrorRetryChecker{DefaultErrorRetryChecker}
+func (sc *SpoofingClient) Poll(req *http.Request, inState ResponseChecker, checkers ...interface{}) (*Response, error) {
+	if len(checkers) == 0 {
+		checkers = []interface{}{ErrorRetryChecker(DefaultErrorRetryChecker)}
 	}
 
 	var resp *Response
@@ -166,11 +174,13 @@ func (sc *SpoofingClient) Poll(req *http.Request, inState ResponseChecker, error
 		defer span.End()
 		rawResp, err := sc.Client.Do(req.WithContext(traceContext))
 		if err != nil {
-			for _, checker := range errorRetryCheckers {
-				retry, newErr := checker(err)
-				if retry {
-					sc.Logf("Retrying %s: %v", req.URL.String(), newErr)
-					return false, nil
+			for _, checker := range checkers {
+				if ec, ok := checker.(ErrorRetryChecker); ok {
+					retry, newErr := ec(err)
+					if retry {
+						sc.Logf("Retrying %s: %v", req.URL.String(), newErr)
+						return false, nil
+					}
 				}
 			}
 			sc.Logf("NOT Retrying %s: %v", req.URL.String(), err)
@@ -190,6 +200,20 @@ func (sc *SpoofingClient) Poll(req *http.Request, inState ResponseChecker, error
 			Header:     rawResp.Header,
 			Body:       body,
 		}
+
+		// This is distinct from inState in that it allows us to uniformly check for
+		// error responses to retry HTTP requests that have technically been successful,
+		// but haven't reached their destination (e.g. got a loadbalancer overload page).
+		for _, checker := range checkers {
+			if rc, ok := checker.(ResponseRetryChecker); ok {
+				retry, newErr := rc(resp)
+				if retry {
+					sc.Logf("Retrying %s: %v", req.URL.String(), newErr)
+					return false, nil
+				}
+			}
+		}
+
 		return inState(resp)
 	})
 
@@ -295,32 +319,13 @@ func (sc *SpoofingClient) endpointState(
 	return f(req, inState)
 }
 
-func (sc *SpoofingClient) Check(req *http.Request, inState ResponseChecker) (*Response, error) {
-	traceContext, span := trace.StartSpan(req.Context(), "SpoofingClient-Trace")
-	defer span.End()
-	rawResp, err := sc.Client.Do(req.WithContext(traceContext))
-	if err != nil {
-		sc.Logf("NOT Retrying %s: %v", req.URL.String(), err)
-		return nil, err
-	}
-	defer rawResp.Body.Close()
-
-	body, err := ioutil.ReadAll(rawResp.Body)
+func (sc *SpoofingClient) Check(req *http.Request, inState ResponseChecker, checkers ...interface{}) (*Response, error) {
+	resp, err := sc.Do(req, checkers...)
 	if err != nil {
 		return nil, err
 	}
-	rawResp.Header.Add(zipkin.ZipkinTraceIDHeader, span.SpanContext().TraceID.String())
 
-	resp := &Response{
-		Status:     rawResp.Status,
-		StatusCode: rawResp.StatusCode,
-		Header:     rawResp.Header,
-		Body:       body,
-	}
 	ok, err := inState(resp)
-
-	sc.logZipkinTrace(resp)
-
 	if err != nil {
 		return resp, fmt.Errorf("response: %s did not pass checks: %w", resp, err)
 	}
@@ -342,7 +347,7 @@ func (sc *SpoofingClient) CheckEndpointState(
 		url,
 		inState,
 		desc,
-		sc.Check,
+		func(req *http.Request, check ResponseChecker) (*Response, error) { return sc.Check(req, check) },
 		"CheckEndpointState",
 		opts...)
 }
diff --git a/test/spoof/spoof_test.go b/test/spoof/spoof_test.go
index 4b5feeb4e..89472dcac 100644
--- a/test/spoof/spoof_test.go
+++ b/test/spoof/spoof_test.go
@@ -20,176 +20,182 @@ package spoof
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 	"net/url"
 
 	"testing"
-)
 
-type fakeTransport struct{}
+	"go.uber.org/atomic"
+)
 
-func (ft *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	return &http.Response{
+var (
+	successResponse = &http.Response{
 		Status:     "200 ok",
 		StatusCode: 200,
 		Header:     http.Header{},
 		Body:       http.NoBody,
-	}, nil
-}
+	}
+	errRetriable    = errors.New("connection reset by peer")
+	errNonRetriable = errors.New("foo")
+)
 
-type countCalls struct {
-	calls int32
+type fakeTransport struct {
+	response *http.Response
+	err      error
+	calls    atomic.Int32
 }
 
-func (c *countCalls) count(rc ResponseChecker) ResponseChecker {
-	return func(resp *Response) (done bool, err error) {
-		c.calls++
-		return rc(resp)
+func (ft *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	call := ft.calls.Inc()
+	if ft.response != nil && call == 2 {
+		// If both a response and an error is defined, we return just the response on
+		// the second call to simulate a retry that passes eventually.
+		return ft.response, nil
 	}
+	return ft.response, ft.err
 }
 
 func TestSpoofingClient_CheckEndpointState(t *testing.T) {
-	type args struct {
-		url     *url.URL
-		inState ResponseChecker
-		desc    string
-		opts    []RequestOption
-	}
 	tests := []struct {
 		name      string
-		args      args
+		transport *fakeTransport
+		inState   ResponseChecker
 		wantErr   bool
 		wantCalls int32
 	}{{
-		name: "Non matching response doesn't trigger a second check",
-		args: args{
-			url: &url.URL{
-				Host:   "fake.knative.net",
-				Scheme: "http",
-			},
-			inState: func(resp *Response) (done bool, err error) {
-				return false, nil
-			},
+		name:      "Non matching response doesn't trigger a second check",
+		transport: &fakeTransport{response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return false, nil
 		},
 		wantErr:   false,
 		wantCalls: 1,
 	}, {
-		name: "Error response doesn't trigger a second check",
-		args: args{
-			url: &url.URL{
-				Host:   "fake.knative.net",
-				Scheme: "http",
-			},
-			inState: func(resp *Response) (done bool, err error) {
-				return false, fmt.Errorf("response error")
-			},
+		name:      "Error response doesn't trigger a second check",
+		transport: &fakeTransport{response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return false, fmt.Errorf("response error")
 		},
 		wantErr:   true,
 		wantCalls: 1,
 	}, {
-		name: "OK response doesn't trigger a second check",
-		args: args{
-			url: &url.URL{
-				Host:   "fake.knative.net",
-				Scheme: "http",
-			},
-			inState: func(resp *Response) (done bool, err error) {
-				return true, nil
-			},
+		name:      "OK response doesn't trigger a second check",
+		transport: &fakeTransport{response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return true, nil
+		},
+		wantErr:   false,
+		wantCalls: 1,
+	}, {
+		name:      "Retriable error is retried",
+		transport: &fakeTransport{err: errRetriable, response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return true, nil
 		},
 		wantErr:   false,
+		wantCalls: 2,
+	}, {
+		name:      "Nonretriable error is not retried",
+		transport: &fakeTransport{err: errNonRetriable, response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return true, nil
+		},
+		wantErr:   true,
 		wantCalls: 1,
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			sc := &SpoofingClient{
-				Client:          &http.Client{Transport: &fakeTransport{}},
+				Client:          &http.Client{Transport: tt.transport},
 				Logf:            t.Logf,
 				RequestInterval: 1,
 				RequestTimeout:  1,
 			}
-			counter := countCalls{}
-			_, err := sc.CheckEndpointState(context.TODO(), tt.args.url, counter.count(tt.args.inState), tt.args.desc, tt.args.opts...)
+			url := &url.URL{
+				Host:   "fake.knative.net",
+				Scheme: "http",
+			}
+			_, err := sc.CheckEndpointState(context.TODO(), url, tt.inState, "")
 			if (err != nil) != tt.wantErr {
 				t.Errorf("SpoofingClient.CheckEndpointState() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if counter.calls != tt.wantCalls {
-				t.Errorf("Expected ResponseChecker to be invoked %d time but got invoked %d", tt.wantCalls, counter.calls)
+			if got, want := tt.transport.calls.Load(), tt.wantCalls; got != want {
+				t.Errorf("Expected Transport to be invoked %d time but got invoked %d", want, got)
 			}
 		})
 	}
 }
 
 func TestSpoofingClient_WaitForEndpointState(t *testing.T) {
-	type args struct {
-		url     *url.URL
-		inState ResponseChecker
-		desc    string
-		opts    []RequestOption
-	}
 	tests := []struct {
 		name      string
-		args      args
+		transport *fakeTransport
+		inState   ResponseChecker
 		wantErr   bool
 		wantCalls int32
 	}{{
-		name: "OK response doesn't trigger a second request",
-		args: args{
-			url: &url.URL{
-				Host:   "fake.knative.net",
-				Scheme: "http",
-			},
-			inState: func(resp *Response) (done bool, err error) {
-				return true, nil
-			},
+		name:      "OK response doesn't trigger a second request",
+		transport: &fakeTransport{response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return true, nil
 		},
 		wantErr:   false,
 		wantCalls: 1,
 	}, {
-		name: "Error response doesn't trigger more requests",
-		args: args{
-			url: &url.URL{
-				Host:   "fake.knative.net",
-				Scheme: "http",
-			},
-			inState: func(resp *Response) (done bool, err error) {
-				return false, fmt.Errorf("response error")
-			},
+		name:      "Error response doesn't trigger more requests",
+		transport: &fakeTransport{response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return false, fmt.Errorf("response error")
 		},
 		wantErr:   true,
 		wantCalls: 1,
 	}, {
-		name: "Non matching response triggers more requests",
-		args: args{
-			url: &url.URL{
-				Host:   "fake.knative.net",
-				Scheme: "http",
-			},
-			inState: func(resp *Response) (done bool, err error) {
-				return false, nil
-			},
+		name:      "Non matching response triggers more requests",
+		transport: &fakeTransport{response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return false, nil
 		},
 		wantErr:   true,
 		wantCalls: 3,
+	}, {
+		name:      "Retriable error is retried",
+		transport: &fakeTransport{err: errRetriable, response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return true, nil
+		},
+		wantErr:   false,
+		wantCalls: 2,
+	}, {
+		name:      "Nonretriable error is not retried",
+		transport: &fakeTransport{err: errNonRetriable, response: successResponse},
+		inState: func(resp *Response) (done bool, err error) {
+			return true, nil
+		},
+		wantErr:   true,
+		wantCalls: 1,
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			sc := &SpoofingClient{
-				Client:          &http.Client{Transport: &fakeTransport{}},
+				Client:          &http.Client{Transport: tt.transport},
 				Logf:            t.Logf,
 				RequestInterval: 1,
 				RequestTimeout:  1,
 			}
-			counter := countCalls{}
-			_, err := sc.WaitForEndpointState(context.TODO(), tt.args.url, counter.count(tt.args.inState), tt.args.desc, tt.args.opts...)
+			url := &url.URL{
+				Host:   "fake.knative.net",
+				Scheme: "http",
+			}
+			_, err := sc.WaitForEndpointState(context.TODO(), url, tt.inState, "")
 			if (err != nil) != tt.wantErr {
 				t.Errorf("SpoofingClient.CheckEndpointState() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if counter.calls != tt.wantCalls {
-				t.Errorf("Expected ResponseChecker to be invoked %d time but got invoked %d", tt.wantCalls, counter.calls)
+			if got, want := tt.transport.calls.Load(), tt.wantCalls; got != want {
+				t.Errorf("Expected Transport to be invoked %d time but got invoked %d", want, got)
 			}
 		})
 	}
diff --git a/webhook/json/decode_test.go b/webhook/json/decode_test.go
new file mode 100644
index 000000000..85006d759
--- /dev/null
+++ b/webhook/json/decode_test.go
@@ -0,0 +1,231 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package json
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"knative.dev/pkg/apis"
+	"knative.dev/pkg/webhook/resourcesemantics"
+)
+
+func TestDecode_KnownFields(t *testing.T) {
+	// We ignore type meta since inline only works with k8s yaml parsers
+	cases := []struct {
+		name  string
+		input string
+
+		want    fixture
+		wantErr bool
+	}{{
+		name:  "no metadata",
+		input: `{}`,
+		want:  fixture{},
+	}, {
+		name:  "known fields",
+		input: `{ "metadata":{"name":"some-name", "namespace":"some-namespace"} }`,
+		want: fixture{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "some-name",
+				Namespace: "some-namespace",
+			},
+		},
+	}, {
+		name: "with spec",
+		input: `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace"},
+			"spec":{"key":"value"}
+		}`,
+		want: fixture{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "some-name",
+				Namespace: "some-namespace",
+			},
+			Spec: map[string]string{"key": "value"},
+		},
+	}, {
+		name: "unknown metadata field",
+		input: `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace", "bomba":"boom"},
+			"spec":{"key":"value"}
+		}`,
+		want: fixture{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "some-name",
+				Namespace: "some-namespace",
+			},
+			Spec: map[string]string{"key": "value"},
+		},
+	}, {
+		name: "unknown field top level",
+		input: `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace"},
+			"bomba": "boom",
+			"spec":{"key":"value"}
+		}`,
+		wantErr: true,
+	}, {
+		name: "multiple metadata keys in the JSON",
+		input: `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace", "bomba":"boom"},
+			"spec":{"metadata":"value"}
+		}`,
+		want: fixture{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "some-name",
+				Namespace: "some-namespace",
+			},
+			Spec: map[string]string{"metadata": "value"},
+		},
+	}, {
+		name: "nested object in metadata",
+		input: `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace", "labels":{"key":"value"}}
+		}`,
+		want: fixture{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "some-name",
+				Namespace: "some-namespace",
+				Labels: map[string]string{
+					"key": "value",
+				},
+			},
+		},
+	}, {
+		name: "nested array in metadata",
+		input: `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace", "finalizers":["first","second"]}
+		}`,
+		want: fixture{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:       "some-name",
+				Namespace:  "some-namespace",
+				Finalizers: []string{"first", "second"},
+			},
+		},
+	}, {
+		name: "bad input",
+		// note use two characters so our decoder fails on the second token lookup
+		input:   "{{",
+		wantErr: true,
+	}}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := &fixture{}
+			err := Decode([]byte(tc.input), got, true)
+
+			if tc.wantErr && err == nil {
+				t.Fatal("DecodeTo() expected an error")
+			} else if !tc.wantErr && err != nil {
+				t.Fatal("unexpected error", err)
+			}
+
+			// Don't bother checking against the fixture if
+			// we expected an error
+			if tc.wantErr {
+				return
+			}
+
+			if diff := cmp.Diff(&tc.want, got); diff != "" {
+				t.Error("unexpected diff", diff)
+			}
+		})
+	}
+}
+
+func TestDecode_AllowUnknownFields(t *testing.T) {
+	input := `{
+			"metadata":{"name":"some-name", "namespace":"some-namespace"},
+			"bomba": "boom",
+			"spec": {"some":"value"}
+		}`
+
+	got := &fixture{}
+	want := &fixture{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "some-name",
+			Namespace: "some-namespace",
+		},
+		Spec: map[string]string{"some": "value"},
+	}
+
+	err := Decode([]byte(input), got, false)
+	if err != nil {
+		t.Fatal("unexpected error", err)
+	}
+
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Error("unexpected diff", diff)
+	}
+}
+
+// Note: this test is paired with the failingFixture and knows
+// that the implementation of Decode parses the json in two passes
+// the first being with an empty metadata '{}' - the second being the real
+// input
+func TestDecode_UnmarshalMetadataFailed(t *testing.T) {
+	input := `{ "metadata":{"name":"some-name", "namespace":"some-namespace"} }`
+	err := Decode([]byte(input), &failingFixture{}, true)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+}
+
+type fixture struct {
+	// Our decoder doesn't support `inline` that's a sig.k8s.io/yaml feature
+	// So we skip parsing this property
+	metav1.TypeMeta   `json:"-"`
+	metav1.ObjectMeta `json:"metadata"`
+	Spec              map[string]string `json:"spec"`
+}
+
+var _ resourcesemantics.GenericCRD = (*fixture)(nil)
+
+func (f *fixture) DeepCopyObject() runtime.Object {
+	panic("not implemented")
+}
+
+func (f *fixture) SetDefaults(context.Context) {
+	panic("not implemented")
+}
+
+func (f *fixture) Validate(context.Context) *apis.FieldError {
+	panic("not implemented")
+}
+
+type failingFixture struct {
+	fixture
+	Meta failingMeta `json:"metadata"`
+}
+
+type failingMeta struct {
+	metav1.ObjectMeta
+}
+
+func (f *failingMeta) UnmarshalJSON(bites []byte) error {
+	if bytes.Equal([]byte("{}"), bites) {
+		return nil
+	}
+	return errors.New("bomba")
+}
diff --git a/webhook/resourcesemantics/defaulting/defaulting_test.go b/webhook/resourcesemantics/defaulting/defaulting_test.go
index a30dc4105..dd5230762 100644
--- a/webhook/resourcesemantics/defaulting/defaulting_test.go
+++ b/webhook/resourcesemantics/defaulting/defaulting_test.go
@@ -187,6 +187,35 @@ func TestUnknownFieldFails(t *testing.T) {
 		`mutation failed: cannot decode incoming new object: json: unknown field "foo"`)
 }
 
+func TestUnknownMetadataFieldSucceeds(t *testing.T) {
+	_, ac := newNonRunningTestResourceAdmissionController(t)
+	req := &admissionv1.AdmissionRequest{
+		Operation: admissionv1.Create,
+		Kind: metav1.GroupVersionKind{
+			Group:   "pkg.knative.dev",
+			Version: "v1alpha1",
+			Kind:    "Resource",
+		},
+	}
+
+	marshaled, err := json.Marshal(map[string]interface{}{
+		"apiVersion": "pkg.knative.dev/v1alpha1",
+		"kind":       "Resource",
+		"metadata": map[string]string{
+			"unknown": "property",
+		},
+		"spec": map[string]string{
+			"fieldWithValidation": "magic value",
+		},
+	})
+	if err != nil {
+		t.Fatal("Failed to marshal resource:", err)
+	}
+	req.Object.Raw = marshaled
+
+	ExpectAllowed(t, ac.Admit(TestContextWithLogger(t), req))
+}
+
 func TestAdmitCreates(t *testing.T) {
 	tests := []struct {
 		name      string
diff --git a/webhook/resourcesemantics/validation/validation_admit_test.go b/webhook/resourcesemantics/validation/validation_admit_test.go
index 009d7d494..43d049b12 100644
--- a/webhook/resourcesemantics/validation/validation_admit_test.go
+++ b/webhook/resourcesemantics/validation/validation_admit_test.go
@@ -213,6 +213,35 @@ func TestUnknownFieldFails(t *testing.T) {
 		`decoding request failed: cannot decode incoming new object: json: unknown field "foo"`)
 }
 
+func TestUnknownMetadataFieldSucceeds(t *testing.T) {
+	_, ac := newNonRunningTestResourceAdmissionController(t)
+	req := &admissionv1.AdmissionRequest{
+		Operation: admissionv1.Create,
+		Kind: metav1.GroupVersionKind{
+			Group:   "pkg.knative.dev",
+			Version: "v1alpha1",
+			Kind:    "Resource",
+		},
+	}
+
+	marshaled, err := json.Marshal(map[string]interface{}{
+		"apiVersion": "pkg.knative.dev/v1alpha1",
+		"kind":       "Resource",
+		"metadata": map[string]string{
+			"unknown": "property",
+		},
+		"spec": map[string]string{
+			"fieldWithValidation": "magic value",
+		},
+	})
+	if err != nil {
+		t.Fatal("Failed to marshal resource:", err)
+	}
+	req.Object.Raw = marshaled
+
+	ExpectAllowed(t, ac.Admit(TestContextWithLogger(t), req))
+}
+
 func TestAdmitCreates(t *testing.T) {
 	tests := []struct {
 		name      string