Repository: crossplane/crossplane

Issue:
enabling realtime composition causes watch error
<!--
Thank you for helping to improve Crossplane!

Please be sure to search for open issues before raising a new one. We use issues
for bug reports and feature requests. Please find us at https://slack.crossplane.io
for questions, support, and discussion.
-->

### What happened?
Upgraded to 0.17.0. I didn't see this issue with 0.16.0
Added --enable-realtime-compositions and the logs fill up with
```
2024-09-11T22:02:04Z	DEBUG	crossplane	Reconciling	{"controller": "defined/compositeresourcedefinition.apiextensions.crossplane.io", "controller": "composite/irsaroles.aws.ops.cake.ai", "request": {"name":"aws-load-balancer"}}
2024-09-11T22:02:04Z	DEBUG	crossplane	Watch exists for GVK, not starting a new one	{"controller": "composite/irsaroles.aws.ops.cake.ai", "watch-type": "ComposedResource", "watched-gvk": "iam.aws.upbound.io/v1beta1, Kind=Policy"}
2024-09-11T22:02:04Z	DEBUG	crossplane	Watch exists for GVK, not starting a new one	{"controller": "composite/irsaroles.aws.ops.cake.ai", "watch-type": "ComposedResource", "watched-gvk": "iam.aws.upbound.io/v1beta1, Kind=RolePolicyAttachment"}
2024-09-11T22:02:04Z	DEBUG	crossplane	Watch exists for GVK, not starting a new one	{"controller": "composite/irsaroles.aws.ops.cake.ai", "watch-type": "ComposedResource", "watched-gvk": "iam.aws.upbound.io/v1beta1, Kind=Role"}
2024-09-11T22:02:04Z	DEBUG	crossplane	Composed resource is not yet ready	{"controller": "defined/compositeresourcedefinition.apiextensions.crossplane.io", "controller": "composite/irsaroles.aws.ops.cake.ai", "request": {"name":"aws-load-balancer"}, "uid": "80af72ae-c827-4101-be6f-b67f6bb61f54", "version": "55906", "name": "aws-load-balancer", "id": "irsaAttachment"}
2024-09-11T22:02:04Z	DEBUG	crossplane	Composed resource is not yet ready	{"controller": "defined/compositeresourcedefinition.apiextensions.crossplane.io", "controller": "composite/irsaroles.aws.ops.cake.ai", "request": {"name":"aws-load-balancer"}, "uid": "80af72ae-c827-4101-be6f-b67f6bb61f54", "version": "55906", "name": "aws-load-balancer", "id": "irsaRole"}
2024-09-11T22:02:04Z	DEBUG	crossplane	Composed resource is not yet ready	{"controller": "defined/compositeresourcedefinition.apiextensions.crossplane.io", "controller": "composite/irsaroles.aws.ops.cake.ai", "request": {"name":"aws-load-balancer"}, "uid": "80af72ae-c827-4101-be6f-b67f6bb61f54", "version": "55906", "name": "aws-load-balancer", "id": "irsaPolicy"}
2024-09-11T22:02:04Z	DEBUG	crossplane	Event(v1.ObjectReference{Kind:"IRSARole", Namespace:"", Name:"aws-load-balancer", UID:"80af72ae-c827-4101-be6f-b67f6bb61f54", APIVersion:"aws.ops.cake.ai/v1alpha1", ResourceVersion:"55906", FieldPath:""}): type: 'Normal' reason: 'ComposeResources' Successfully composed resources
2024-09-11T22:02:04Z	DEBUG	crossplane	Event(v1.ObjectReference{Kind:"IRSARole", Namespace:"", Name:"aws-load-balancer", UID:"80af72ae-c827-4101-be6f-b67f6bb61f54", APIVersion:"aws.ops.cake.ai/v1alpha1", ResourceVersion:"55906", FieldPath:""}): type: 'Normal' reason: 'ComposeResources' Composed resource "irsaAttachment" is not yet ready
2024-09-11T22:02:04Z	DEBUG	crossplane	Event(v1.ObjectReference{Kind:"IRSARole", Namespace:"", Name:"aws-load-balancer", UID:"80af72ae-c827-4101-be6f-b67f6bb61f54", APIVersion:"aws.ops.cake.ai/v1alpha1", ResourceVersion:"55906", FieldPath:""}): type: 'Normal' reason: 'ComposeResources' Composed resource "irsaRole" is not yet ready
2024-09-11T22:02:04Z	DEBUG	crossplane	Event(v1.ObjectReference{Kind:"IRSARole", Namespace:"", Name:"aws-load-balancer", UID:"80af72ae-c827-4101-be6f-b67f6bb61f54", APIVersion:"aws.ops.cake.ai/v1alpha1", ResourceVersion:"55906", FieldPath:""}): type: 'Normal' reason: 'ComposeResources' Composed resource "irsaPolicy" is not yet ready
2024-09-11T22:02:10Z	ERROR	crossplane	pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:232: expected type *composed.Unstructured, but watch event object had type *unstructured.Unstructured
k8s.io/client-go/tools/cache.(*Reflector).watch
	/go/pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:445
k8s.io/client-go/tools/cache.(*Reflector).ListAndWatch
	/go/pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:365
k8s.io/client-go/tools/cache.(*Reflector).Run.func1
	/go/pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:298
k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1
	/go/pkg/mod/k8s.io/apimachinery@v0.30.0/pkg/util/wait/backoff.go:226
k8s.io/apimachinery/pkg/util/wait.BackoffUntil
	/go/pkg/mod/k8s.io/apimachinery@v0.30.0/pkg/util/wait/backoff.go:227
k8s.io/client-go/tools/cache.(*Reflector).Run
	/go/pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:297
k8s.io/client-go/tools/cache.(*controller).Run.(*Group).StartWithChannel.func2
	/go/pkg/mod/k8s.io/apimachinery@v0.30.0/pkg/util/wait/wait.go:55
k8s.io/apimachinery/pkg/util/wait.(*Group).Start.func1
	/go/pkg/mod/k8s.io/apimachinery@v0.30.0/pkg/util/wait/wait.go:72
```

I am trying to debug why it takes 20 minutes before my IAM resources are touched. I thought adding realtime-compositions would do something but it seems the aws-iam-provider doesn't actually do anything for the first 10m nor does it watch for events which is a separate issue.

### How can we reproduce it?
Not sure. I have my own composition which is just creating a AWS role, policy and policy attachment.

### What environment did it happen in?
Crossplane version:  0.17.0
EKS 1.30.2

Discussion:
@github-actions: Crossplane does not currently have enough maintainers to address every issue and pull request. This issue has been automatically marked as `stale` because it has had no activity in the last 90 days. It will be closed in 14 days if no further activity occurs. Leaving a comment **starting with** `/fresh` will mark this issue as not stale.@negz: > Upgraded to 0.17.0

@fraenkel I'm guessing you mean Crossplane v1.17 (not 0.17), right?@fraenkel: Correct. But I haven't tried since. @negz: I can't immediately reproduce this on v1.17.0.

My guess is what's happening is that...

We pass a `*composed.Unstructured` when starting a watch:

https://github.com/crossplane/crossplane/blob/0e973c9fabe336bf7a82abb0752869d27abadabc/internal/controller/apiextensions/composite/reconciler.go#L695-L706

Which in turn passes that type to `GetInformer`, which starts an informer. So the informer (backed by the cache reflector from the error) is expecting events to pertain to a `*composed.Unstructured`:

https://github.com/crossplane/crossplane/blob/0e973c9fabe336bf7a82abb0752869d27abadabc/internal/engine/engine.go#L402
https://github.com/crossplane/crossplane/blob/0e973c9fabe336bf7a82abb0752869d27abadabc/internal/engine/source.go#L55

However that type just wraps `*unstructured.Unstructured`:

https://github.com/crossplane/crossplane-runtime/blob/db9545b7a/pkg/resource/unstructured/composed/composed.go#L63-L66

So something's unwrapping that type unexpectedly, which is confusing the cache reflector?

Given this seems to pertain to watches of composed resources, I hoped I'd be able to easily replicate it by:

1. Creating an XR on v1.17.0
2. Letting it compose an MR
3. Editing the MR's annotations to trigger a watch update

That seems to work fine though - I don't see any errors in the logs.@negz: https://github.com/crossplane/crossplane/blob/0e973c9fabe336bf7a82abb0752869d27abadabc/go.mod#L41
https://github.com/crossplane/crossplane/blob/0e973c9fabe336bf7a82abb0752869d27abadabc/go.mod#L12

https://github.com/kubernetes-sigs/controller-runtime/blob/v0.18.2/pkg/cache/internal/informers.go#L428

That said, it looks like controller-runtime should've detected that `composed.Unstructured` was in fact unstructured (it satisfies the `runtime.Unstructured` interface by embedding `unstructured.Unstructured`) and setup an unstructured ListWatch (reflector) accordingly. 🤔 @negz: Given we've only had one report of this and that I can't reproduce it I'm going to close this issue. I'll look into it again if and when we can reproduce it.@negz: @chlunde just hit this with https://github.com/crossplane/crossplane/releases/tag/v1.20.0-rc.1. So it still exists...


```
crossplane-848f489699-jmpvd crossplane 2025-05-14T19:49:29Z     ERROR   crossplane      Unhandled Error {"logger": "UnhandledError", "err
or": "pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:243: expected type *composed.Unstructured, but watch event object had type *unstructured.Unstructured"}
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.handleWatch
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:760
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.(*Reflector).watch
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:458
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.(*Reflector).watchWithResync
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:406
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.(*Reflector).ListAndWatch
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:369
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.(*Reflector).Run.func1
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:307
crossplane-848f489699-jmpvd crossplane k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/apimachinery@v0.31.2/pkg/util/wait/backoff.go:226
crossplane-848f489699-jmpvd crossplane k8s.io/apimachinery/pkg/util/wait.BackoffUntil
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/apimachinery@v0.31.2/pkg/util/wait/backoff.go:227
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.(*Reflector).Run
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/client-go@v0.31.2/tools/cache/reflector.go:306
crossplane-848f489699-jmpvd crossplane k8s.io/client-go/tools/cache.(*controller).Run.(*Group).StartWithChannel.func2
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/apimachinery@v0.31.2/pkg/util/wait/wait.go:55
crossplane-848f489699-jmpvd crossplane k8s.io/apimachinery/pkg/util/wait.(*Group).Start.func1
crossplane-848f489699-jmpvd crossplane  /go/pkg/mod/k8s.io/apimachinery@v0.31.2/pkg/util/wait/wait.go:72
```@jbw976: It's possible (but not sure yet) that this issue could also be causing the behavior seen in https://github.com/crossplane/crossplane/issues/6455#issuecomment-2887385111.@jbw976: I have a theory for this bug that I will try to test out in practice soon, but wanted to share here now. We will start watches for an XR of a particular type in three places that I know of:

1. In the definition reconciler, when the XRD that defines the XR is created: [definition/reconciler.go#L514-L524](https://github.com/crossplane/crossplane/blob/release-1.20/internal/controller/apiextensions/definition/reconciler.go#L514-L524)
    * here we use `kunstructured.Unstructured`
1. In the offered reconciler, when a claim type is first created: [offered/reconciler.go#L493-L504](https://github.com/crossplane/crossplane/blob/release-1.20/internal/controller/apiextensions/offered/reconciler.go#L493-L504)
    * here we use `kunstructured.Unstructured` 
1. In the composite reconciler, when starting watches for all the composed resources of an XR. If any of those composed resources are nested XRs themselves, they are included here too: [composite/reconciler.go#L617-L624](https://github.com/crossplane/crossplane/blob/release-1.20/internal/controller/apiextensions/composite/reconciler.go#L617-L624)
    * here we are instead using `composed.Unstructured`

I'm thinking that starting watches for the same underlying XR GVK, but using different Go types (`kunstructured.Unstructured` vs. `composed.Unstructured`) could be causing this issue.  We do [disambiguate our watch IDs](https://github.com/crossplane/crossplane/blob/release-1.20/internal/engine/engine.go#L436) with the [watch type](https://github.com/crossplane/crossplane/blob/release-1.20/internal/engine/engine.go#L319-L332) (e.g. claim, composed resources, etc.), but I think controller-runtime only [really considers GVK](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.19.0/pkg/cache/informer_cache.go#L169) when getting the informer for the type.

So perhaps we start a watch for the XR type using `kunstructured.Unstructured`, then start another watch for the same XR type using `composed.Unstructured` when it is a composed resource for another XR, then controller-runtime reuses the same underlying informer for both, and boom confusion about the expected event object type? 🤔 

> Letting it compose an MR

Perhaps it would have been reproducible for you @negz if you had tested with an XR that composes another XR? I'll try that scenario to find out more here, but let me know if I'm already off base with this theory 😂 @negz: @jbw976 Your theory sounds promising, but our E2E test covers that case. It composes a nested XR, of the same type as the parent: 🤔 

https://github.com/crossplane/crossplane/blob/149fcf55c3143546cb520d629ac2f330b4665669/test/e2e/manifests/apiextensions/composition/functions/setup/composition.yaml#L28-L35

So I'd expect our tests to be first setting up an XR watch for `XNopResource`, then setting up a composed resource watch for `XNopResource`.

That said, it should be easy to setup the composed resource watch using `*kunstructured.Unstructured` instead given `*composed.Unstructured` just wraps that type. Maybe we should do that and see if it helps @duizabojul or @chlunde.@chlunde: Probably not related, but I'm writing it here as it was an issue in this cluster (where also provider-kubernetes was missing webhooked). I noticed one object that for some reason had a leftover `resourceRefs` from development of a composition:

```yaml
  resourceRefs:
  -  apiVersion: networking.istio.io/v1
     kind: ServiceEntry
     name: keycloak-pgadmin
```

This should've been a provider-kubernetes Object, but it wasn't. The composition was fixed a long time ago but this one instance was not fixed and had not healed itself.@chlunde: The error is gone now 🤷 

List of (unlikely related stuff) that I fixed in this lab/test cluster:

* missing webhook port for provider-kubernetes
* leaked Usage objects
* leaked Object
* a composition with a `spec.parameters.foo: "foo*"` which now said that `*` is an invalid character via CEL
* a composition trying to render an MR which did not match the schema of that MR
* the resourceRefs pointing to a namespaced non-crossplane resource
* upgraded via 1.19@jbw976: I can reproduce this consistently now by using a nested XR that composes some MRs itself @negz 💪 - I don't know why my scenario makes this issue appear consistently while it doesn't seem to happen in the e2e test, but at least we have something that we can test a fix on and gain some confidence that we've actually fixed it. Perhaps the unhandled error does actually happen in the e2e test but it isn't logged and/or the way readiness is set/detected isn't affected by losing the watch. Just some ideas, not much concrete there...

I'll work on the code fix to standardize on using `unstructured.Unstructured` and see how that goes. I'll also share my repro scenario and manifests soon too. Let me know if you think there's a better fix to pursue @negz.

@chlunde thank you for sharing the issues you fixed in your control plane, we appreciate hearing about those as well 🙇‍♂ @jbw976: https://github.com/crossplane/crossplane/pull/6462 has been opened to hopefully fix this issue 🙏 

The repro steps and manifests I've been using can be found in https://github.com/jbw976/debug-xp-rtc-watch-error. 

Solution patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 390b8bf60..c0f2d0b3a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -247,6 +247,7 @@ jobs:
           - environment-configs
           - usage
           - ssa-claims
+          - realtime-compositions
 
     steps:
       - name: Setup QEMU
diff --git a/.golangci.yml b/.golangci.yml
index c62817d9a..7e46dbe35 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -103,6 +103,10 @@ linters:
     # to communicate what the bool means.
     - nonamedreturns
 
+    # Warns about taking the address of a range variable. This isn't an issue in
+    # Go v1.22 and above: https://tip.golang.org/doc/go1.22
+    - exportloopref
+
 linters-settings:
   errcheck:
     # report about not checking of errors in type assetions: `a := b.(MyStruct)`;
@@ -224,6 +228,7 @@ issues:
   # Excluding generated files.
   exclude-files:
   - "zz_generated\\..+\\.go$"
+  - ".+\\.pb.go$"
   # Excluding configuration per-path and per-linter.
   exclude-rules:
     # Exclude some linters from running on tests files.
@@ -282,6 +287,13 @@ issues:
       linters:
       - gosec
       - gas
+
+    # This is about implicit memory aliasing in a range loop.
+    # This is a false positive with Go v1.22 and above.
+    - text: "G601:"
+      linters:
+      - gosec
+      - gas
     
     # Some k8s dependencies do not have JSON tags on all fields in structs.
     - path: k8s.io/
diff --git a/Makefile b/Makefile
index dafd154e6..b9595a1e7 100644
--- a/Makefile
+++ b/Makefile
@@ -81,6 +81,15 @@ fallthrough: submodules
 	@make
 
 CRD_DIR = cluster/crds
+CRD_PATCH_DIR = cluster/crd-patches
+
+# See patch files for details.
+crds.patch: $(KUBECTL)
+	@$(INFO) patching generated CRDs
+	@mkdir $(WORK_DIR)/patch
+	@$(KUBECTL) patch --local --type=json -f $(CRD_DIR)/pkg.crossplane.io_deploymentruntimeconfigs.yaml --patch-file $(CRD_PATCH_DIR)/pkg.crossplane.io_deploymentruntimeconfigs.yaml -o yaml > $(WORK_DIR)/patch/pkg.crossplane.io_deploymentruntimeconfigs.yaml
+	@mv $(WORK_DIR)/patch/pkg.crossplane.io_deploymentruntimeconfigs.yaml $(CRD_DIR)/pkg.crossplane.io_deploymentruntimeconfigs.yaml
+	@$(OK) patched generated CRDs
 
 crds.clean:
 	@$(INFO) cleaning generated CRDs
@@ -93,7 +102,7 @@ generate.run: gen-kustomize-crds gen-chart-license
 gen-chart-license:
 	@cp -f LICENSE cluster/charts/crossplane/LICENSE
 
-generate.done: crds.clean
+generate.done: crds.clean crds.patch
 
 gen-kustomize-crds:
 	@$(INFO) Adding all CRDs to Kustomize file for local development
diff --git a/apis/pkg/v1/interfaces.go b/apis/pkg/v1/interfaces.go
index 2b9500084..bcb00885e 100644
--- a/apis/pkg/v1/interfaces.go
+++ b/apis/pkg/v1/interfaces.go
@@ -726,7 +726,6 @@ type PackageRevisionList interface {
 func (p *ProviderRevisionList) GetRevisions() []PackageRevision {
 	prs := make([]PackageRevision, len(p.Items))
 	for i, r := range p.Items {
-		r := r // Pin range variable so we can take its address.
 		prs[i] = &r
 	}
 	return prs
@@ -736,7 +735,6 @@ func (p *ProviderRevisionList) GetRevisions() []PackageRevision {
 func (p *ConfigurationRevisionList) GetRevisions() []PackageRevision {
 	prs := make([]PackageRevision, len(p.Items))
 	for i, r := range p.Items {
-		r := r // Pin range variable so we can take its address.
 		prs[i] = &r
 	}
 	return prs
diff --git a/apis/pkg/v1beta1/function_interfaces.go b/apis/pkg/v1beta1/function_interfaces.go
index 4166f4f16..7e99faf1c 100644
--- a/apis/pkg/v1beta1/function_interfaces.go
+++ b/apis/pkg/v1beta1/function_interfaces.go
@@ -328,7 +328,6 @@ func (r *FunctionRevision) SetCommonLabels(l map[string]string) {
 func (p *FunctionRevisionList) GetRevisions() []v1.PackageRevision {
 	prs := make([]v1.PackageRevision, len(p.Items))
 	for i, r := range p.Items {
-		r := r // Pin range variable so we can take its address.
 		prs[i] = &r
 	}
 	return prs
diff --git a/apis/pkg/v1beta1/lock.go b/apis/pkg/v1beta1/lock.go
index 955d765a8..c6c102146 100644
--- a/apis/pkg/v1beta1/lock.go
+++ b/apis/pkg/v1beta1/lock.go
@@ -60,7 +60,6 @@ type LockPackage struct {
 func ToNodes(pkgs ...LockPackage) []dag.Node {
 	nodes := make([]dag.Node, len(pkgs))
 	for i, r := range pkgs {
-		r := r // Pin range variable so we can take its address.
 		nodes[i] = &r
 	}
 	return nodes
@@ -75,7 +74,6 @@ func (l *LockPackage) Identifier() string {
 func (l *LockPackage) Neighbors() []dag.Node {
 	nodes := make([]dag.Node, len(l.Dependencies))
 	for i, r := range l.Dependencies {
-		r := r // Pin range variable so we can take its address.
 		nodes[i] = &r
 	}
 	return nodes
diff --git a/cluster/crd-patches/pkg.crossplane.io_deploymentruntimeconfigs.yaml b/cluster/crd-patches/pkg.crossplane.io_deploymentruntimeconfigs.yaml
new file mode 100644
index 000000000..57e3dd1e1
--- /dev/null
+++ b/cluster/crd-patches/pkg.crossplane.io_deploymentruntimeconfigs.yaml
@@ -0,0 +1,15 @@
+# For reasons that aren't immediately obvious, updating k8s.io/code-generator
+# from v0.29.x to v0.30 triggers a variant of the below issue. As far as I can
+# tell, this is the only way to work around it. The below fields are list map
+# keys, but aren't required in the generated CRD.
+# https://github.com/kubernetes-sigs/controller-tools/issues/444
+
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/deploymentTemplate/properties/spec/properties/template/properties/spec/properties/hostAliases/items/required
+  value:
+  - ip
+
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/deploymentTemplate/properties/spec/properties/template/properties/spec/properties/imagePullSecrets/items/required
+  value:
+  - name
\ No newline at end of file
diff --git a/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml b/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml
index 99c507489..6b6af162d 100644
--- a/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml
@@ -79,6 +79,7 @@ spec:
                     items:
                       type: string
                     type: array
+                    x-kubernetes-list-type: atomic
                   kind:
                     description: |-
                       kind is the serialized kind of the resource. It is normally CamelCase and singular.
@@ -103,6 +104,7 @@ spec:
                     items:
                       type: string
                     type: array
+                    x-kubernetes-list-type: atomic
                   singular:
                     description: singular is the singular name of the resource. It
                       must be all lowercase. Defaults to lowercased `kind`.
@@ -222,6 +224,7 @@ spec:
                         items:
                           type: string
                         type: array
+                        x-kubernetes-list-type: atomic
                     required:
                     - conversionReviewVersions
                     type: object
@@ -312,6 +315,7 @@ spec:
                     items:
                       type: string
                     type: array
+                    x-kubernetes-list-type: atomic
                   kind:
                     description: |-
                       kind is the serialized kind of the resource. It is normally CamelCase and singular.
@@ -336,6 +340,7 @@ spec:
                     items:
                       type: string
                     type: array
+                    x-kubernetes-list-type: atomic
                   singular:
                     description: singular is the singular name of the resource. It
                       must be all lowercase. Defaults to lowercased `kind`.
diff --git a/cluster/crds/pkg.crossplane.io_configurationrevisions.yaml b/cluster/crds/pkg.crossplane.io_configurationrevisions.yaml
index 7fb52c93e..bd101a2e8 100644
--- a/cluster/crds/pkg.crossplane.io_configurationrevisions.yaml
+++ b/cluster/crds/pkg.crossplane.io_configurationrevisions.yaml
@@ -242,6 +242,7 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     nonResourceURLs:
                       description: |-
                         NonResourceURLs is a set of partial urls that a user should have access to.  *s are allowed, but only as the full, final step in the path
@@ -250,6 +251,7 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     resourceNames:
                       description: ResourceNames is an optional white list of names
                         that the rule applies to.  An empty set means that everything
@@ -257,18 +259,21 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     resources:
                       description: Resources is a list of resources this rule applies
                         to. '*' represents all resources.
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     verbs:
                       description: Verbs is a list of Verbs that apply to ALL the
                         ResourceKinds contained in this rule. '*' represents all verbs.
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                   required:
                   - verbs
                   type: object
diff --git a/cluster/crds/pkg.crossplane.io_controllerconfigs.yaml b/cluster/crds/pkg.crossplane.io_controllerconfigs.yaml
index 3cee9ece8..cf23a2caf 100644
--- a/cluster/crds/pkg.crossplane.io_controllerconfigs.yaml
+++ b/cluster/crds/pkg.crossplane.io_controllerconfigs.yaml
@@ -111,11 +111,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 matchFields:
                                   description: A list of node selector requirements
                                     by node's fields.
@@ -143,11 +145,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                               type: object
                               x-kubernetes-map-type: atomic
                             weight:
@@ -160,6 +164,7 @@ spec:
                           - weight
                           type: object
                         type: array
+                        x-kubernetes-list-type: atomic
                       requiredDuringSchedulingIgnoredDuringExecution:
                         description: |-
                           If the affinity requirements specified by this field are not met at
@@ -204,11 +209,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 matchFields:
                                   description: A list of node selector requirements
                                     by node's fields.
@@ -236,14 +243,17 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                               type: object
                               x-kubernetes-map-type: atomic
                             type: array
+                            x-kubernetes-list-type: atomic
                         required:
                         - nodeSelectorTerms
                         type: object
@@ -304,11 +314,13 @@ spec:
                                             items:
                                               type: string
                                             type: array
+                                            x-kubernetes-list-type: atomic
                                         required:
                                         - key
                                         - operator
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     matchLabels:
                                       additionalProperties:
                                         type: string
@@ -323,12 +335,12 @@ spec:
                                   description: |-
                                     MatchLabelKeys is a set of pod label keys to select which pods will
                                     be taken into consideration. The keys are used to lookup values from the
-                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                     to select the group of existing pods which pods will be taken into consideration
                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                     pod labels will be ignored. The default value is empty.
-                                    The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                    Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                   items:
                                     type: string
@@ -338,12 +350,12 @@ spec:
                                   description: |-
                                     MismatchLabelKeys is a set of pod label keys to select which pods will
                                     be taken into consideration. The keys are used to lookup values from the
-                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                     to select the group of existing pods which pods will be taken into consideration
                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                     pod labels will be ignored. The default value is empty.
-                                    The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                    Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                   items:
                                     type: string
@@ -384,11 +396,13 @@ spec:
                                             items:
                                               type: string
                                             type: array
+                                            x-kubernetes-list-type: atomic
                                         required:
                                         - key
                                         - operator
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     matchLabels:
                                       additionalProperties:
                                         type: string
@@ -408,6 +422,7 @@ spec:
                                   items:
                                     type: string
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 topologyKey:
                                   description: |-
                                     This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -430,6 +445,7 @@ spec:
                           - weight
                           type: object
                         type: array
+                        x-kubernetes-list-type: atomic
                       requiredDuringSchedulingIgnoredDuringExecution:
                         description: |-
                           If the affinity requirements specified by this field are not met at
@@ -479,11 +495,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 matchLabels:
                                   additionalProperties:
                                     type: string
@@ -498,12 +516,12 @@ spec:
                               description: |-
                                 MatchLabelKeys is a set of pod label keys to select which pods will
                                 be taken into consideration. The keys are used to lookup values from the
-                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                 to select the group of existing pods which pods will be taken into consideration
                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                 pod labels will be ignored. The default value is empty.
-                                The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                               items:
                                 type: string
@@ -513,12 +531,12 @@ spec:
                               description: |-
                                 MismatchLabelKeys is a set of pod label keys to select which pods will
                                 be taken into consideration. The keys are used to lookup values from the
-                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                 to select the group of existing pods which pods will be taken into consideration
                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                 pod labels will be ignored. The default value is empty.
-                                The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                               items:
                                 type: string
@@ -558,11 +576,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 matchLabels:
                                   additionalProperties:
                                     type: string
@@ -582,6 +602,7 @@ spec:
                               items:
                                 type: string
                               type: array
+                              x-kubernetes-list-type: atomic
                             topologyKey:
                               description: |-
                                 This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -594,6 +615,7 @@ spec:
                           - topologyKey
                           type: object
                         type: array
+                        x-kubernetes-list-type: atomic
                     type: object
                   podAntiAffinity:
                     description: Describes pod anti-affinity scheduling rules (e.g.
@@ -651,11 +673,13 @@ spec:
                                             items:
                                               type: string
                                             type: array
+                                            x-kubernetes-list-type: atomic
                                         required:
                                         - key
                                         - operator
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     matchLabels:
                                       additionalProperties:
                                         type: string
@@ -670,12 +694,12 @@ spec:
                                   description: |-
                                     MatchLabelKeys is a set of pod label keys to select which pods will
                                     be taken into consideration. The keys are used to lookup values from the
-                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                     to select the group of existing pods which pods will be taken into consideration
                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                     pod labels will be ignored. The default value is empty.
-                                    The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                    Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                   items:
                                     type: string
@@ -685,12 +709,12 @@ spec:
                                   description: |-
                                     MismatchLabelKeys is a set of pod label keys to select which pods will
                                     be taken into consideration. The keys are used to lookup values from the
-                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                     to select the group of existing pods which pods will be taken into consideration
                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                     pod labels will be ignored. The default value is empty.
-                                    The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                    Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                   items:
                                     type: string
@@ -731,11 +755,13 @@ spec:
                                             items:
                                               type: string
                                             type: array
+                                            x-kubernetes-list-type: atomic
                                         required:
                                         - key
                                         - operator
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     matchLabels:
                                       additionalProperties:
                                         type: string
@@ -755,6 +781,7 @@ spec:
                                   items:
                                     type: string
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 topologyKey:
                                   description: |-
                                     This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -777,6 +804,7 @@ spec:
                           - weight
                           type: object
                         type: array
+                        x-kubernetes-list-type: atomic
                       requiredDuringSchedulingIgnoredDuringExecution:
                         description: |-
                           If the anti-affinity requirements specified by this field are not met at
@@ -826,11 +854,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 matchLabels:
                                   additionalProperties:
                                     type: string
@@ -845,12 +875,12 @@ spec:
                               description: |-
                                 MatchLabelKeys is a set of pod label keys to select which pods will
                                 be taken into consideration. The keys are used to lookup values from the
-                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                 to select the group of existing pods which pods will be taken into consideration
                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                 pod labels will be ignored. The default value is empty.
-                                The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                               items:
                                 type: string
@@ -860,12 +890,12 @@ spec:
                               description: |-
                                 MismatchLabelKeys is a set of pod label keys to select which pods will
                                 be taken into consideration. The keys are used to lookup values from the
-                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                 to select the group of existing pods which pods will be taken into consideration
                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                 pod labels will be ignored. The default value is empty.
-                                The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                               items:
                                 type: string
@@ -905,11 +935,13 @@ spec:
                                         items:
                                           type: string
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     required:
                                     - key
                                     - operator
                                     type: object
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 matchLabels:
                                   additionalProperties:
                                     type: string
@@ -929,6 +961,7 @@ spec:
                               items:
                                 type: string
                               type: array
+                              x-kubernetes-list-type: atomic
                             topologyKey:
                               description: |-
                                 This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -941,6 +974,7 @@ spec:
                           - topologyKey
                           type: object
                         type: array
+                        x-kubernetes-list-type: atomic
                     type: object
                 type: object
               args:
@@ -1195,6 +1229,29 @@ spec:
                   PodSecurityContext holds pod-level security attributes and common container settings.
                   Optional: Defaults to empty.  See type description for default values of each field.
                 properties:
+                  appArmorProfile:
+                    description: |-
+                      appArmorProfile is the AppArmor options to use by the containers in this pod.
+                      Note that this field cannot be set when spec.os.name is windows.
+                    properties:
+                      localhostProfile:
+                        description: |-
+                          localhostProfile indicates a profile loaded on the node that should be used.
+                          The profile must be preconfigured on the node to work.
+                          Must match the loaded name of the profile.
+                          Must be set if and only if type is "Localhost".
+                        type: string
+                      type:
+                        description: |-
+                          type indicates which kind of AppArmor profile will be applied.
+                          Valid options are:
+                            Localhost - a profile pre-loaded on the node.
+                            RuntimeDefault - the container runtime's default profile.
+                            Unconfined - no AppArmor enforcement.
+                        type: string
+                    required:
+                    - type
+                    type: object
                   fsGroup:
                     description: |-
                       A special supplemental group that applies to all containers in a pod.
@@ -1314,6 +1371,7 @@ spec:
                       format: int64
                       type: integer
                     type: array
+                    x-kubernetes-list-type: atomic
                   sysctls:
                     description: |-
                       Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
@@ -1333,6 +1391,7 @@ spec:
                       - value
                       type: object
                     type: array
+                    x-kubernetes-list-type: atomic
                   windowsOptions:
                     description: |-
                       The Windows specific settings applied to all containers.
@@ -1505,6 +1564,30 @@ spec:
                       2) has CAP_SYS_ADMIN
                       Note that this field cannot be set when spec.os.name is windows.
                     type: boolean
+                  appArmorProfile:
+                    description: |-
+                      appArmorProfile is the AppArmor options to use by this container. If set, this profile
+                      overrides the pod's appArmorProfile.
+                      Note that this field cannot be set when spec.os.name is windows.
+                    properties:
+                      localhostProfile:
+                        description: |-
+                          localhostProfile indicates a profile loaded on the node that should be used.
+                          The profile must be preconfigured on the node to work.
+                          Must match the loaded name of the profile.
+                          Must be set if and only if type is "Localhost".
+                        type: string
+                      type:
+                        description: |-
+                          type indicates which kind of AppArmor profile will be applied.
+                          Valid options are:
+                            Localhost - a profile pre-loaded on the node.
+                            RuntimeDefault - the container runtime's default profile.
+                            Unconfined - no AppArmor enforcement.
+                        type: string
+                    required:
+                    - type
+                    type: object
                   capabilities:
                     description: |-
                       The capabilities to add/drop when running containers.
@@ -1517,12 +1600,14 @@ spec:
                           description: Capability represent POSIX capabilities type
                           type: string
                         type: array
+                        x-kubernetes-list-type: atomic
                       drop:
                         description: Removed capabilities
                         items:
                           description: Capability represent POSIX capabilities type
                           type: string
                         type: array
+                        x-kubernetes-list-type: atomic
                     type: object
                   privileged:
                     description: |-
@@ -1730,6 +1815,8 @@ spec:
                         to container and the other way around.
                         When not set, MountPropagationNone is used.
                         This field is beta in 1.10.
+                        When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                        (which defaults to None).
                       type: string
                     name:
                       description: This must match the Name of a Volume.
@@ -1739,6 +1826,29 @@ spec:
                         Mounted read-only if true, read-write otherwise (false or unspecified).
                         Defaults to false.
                       type: boolean
+                    recursiveReadOnly:
+                      description: |-
+                        RecursiveReadOnly specifies whether read-only mounts should be handled
+                        recursively.
+
+
+                        If ReadOnly is false, this field has no meaning and must be unspecified.
+
+
+                        If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                        recursively read-only.  If this field is set to IfPossible, the mount is made
+                        recursively read-only, if it is supported by the container runtime.  If this
+                        field is set to Enabled, the mount is made recursively read-only if it is
+                        supported by the container runtime, otherwise the pod will not be started and
+                        an error will be generated to indicate the reason.
+
+
+                        If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                        None (or be unspecified, which defaults to None).
+
+
+                        If this field is not specified, it is treated as an equivalent of Disabled.
+                      type: string
                     subPath:
                       description: |-
                         Path within the volume from which the container's volume should be mounted.
@@ -1867,6 +1977,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         path:
                           description: 'path is Optional: Used as the mounted root,
                             rather than the full Ceph tree, default is /'
@@ -1994,6 +2105,7 @@ spec:
                             - path
                             type: object
                           type: array
+                          x-kubernetes-list-type: atomic
                         name:
                           description: |-
                             Name of the referent.
@@ -2077,8 +2189,8 @@ spec:
                             properties:
                               fieldRef:
                                 description: 'Required: Selects a field of the pod:
-                                  only annotations, labels, name and namespace are
-                                  supported.'
+                                  only annotations, labels, name, namespace and uid
+                                  are supported.'
                                 properties:
                                   apiVersion:
                                     description: Version of the schema the FieldPath
@@ -2137,6 +2249,7 @@ spec:
                             - path
                             type: object
                           type: array
+                          x-kubernetes-list-type: atomic
                       type: object
                     emptyDir:
                       description: |-
@@ -2259,6 +2372,7 @@ spec:
                                   items:
                                     type: string
                                   type: array
+                                  x-kubernetes-list-type: atomic
                                 dataSource:
                                   description: |-
                                     dataSource field can be used to specify either:
@@ -2403,11 +2517,13 @@ spec:
                                             items:
                                               type: string
                                             type: array
+                                            x-kubernetes-list-type: atomic
                                         required:
                                         - key
                                         - operator
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     matchLabels:
                                       additionalProperties:
                                         type: string
@@ -2435,7 +2551,7 @@ spec:
                                     If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be
                                     set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource
                                     exists.
-                                    More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass
+                                    More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/
                                     (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled.
                                   type: string
                                 volumeMode:
@@ -2479,6 +2595,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         wwids:
                           description: |-
                             wwids Optional: FC volume world wide identifiers (wwids)
@@ -2486,6 +2603,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                       type: object
                     flexVolume:
                       description: |-
@@ -2706,6 +2824,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         readOnly:
                           description: |-
                             readOnly here will force the ReadOnly setting in VolumeMounts.
@@ -2894,11 +3013,13 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           required:
                                           - key
                                           - operator
                                           type: object
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                       matchLabels:
                                         additionalProperties:
                                           type: string
@@ -2977,6 +3098,7 @@ spec:
                                       - path
                                       type: object
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                   name:
                                     description: |-
                                       Name of the referent.
@@ -3004,7 +3126,7 @@ spec:
                                         fieldRef:
                                           description: 'Required: Selects a field
                                             of the pod: only annotations, labels,
-                                            name and namespace are supported.'
+                                            name, namespace and uid are supported.'
                                           properties:
                                             apiVersion:
                                               description: Version of the schema the
@@ -3067,6 +3189,7 @@ spec:
                                       - path
                                       type: object
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                 type: object
                               secret:
                                 description: secret information about the secret data
@@ -3110,6 +3233,7 @@ spec:
                                       - path
                                       type: object
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                   name:
                                     description: |-
                                       Name of the referent.
@@ -3153,6 +3277,7 @@ spec:
                                 type: object
                             type: object
                           type: array
+                          x-kubernetes-list-type: atomic
                       type: object
                     quobyte:
                       description: quobyte represents a Quobyte mount on the host
@@ -3223,6 +3348,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         pool:
                           description: |-
                             pool is the rados pool name.
@@ -3377,6 +3503,7 @@ spec:
                             - path
                             type: object
                           type: array
+                          x-kubernetes-list-type: atomic
                         optional:
                           description: optional field specify whether the Secret or
                             its keys must be defined
diff --git a/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml b/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml
index 7b80a697f..35a599ab7 100644
--- a/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml
+++ b/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml
@@ -152,11 +152,13 @@ spec:
                                   items:
                                     type: string
                                   type: array
+                                  x-kubernetes-list-type: atomic
                               required:
                               - key
                               - operator
                               type: object
                             type: array
+                            x-kubernetes-list-type: atomic
                           matchLabels:
                             additionalProperties:
                               type: string
@@ -312,11 +314,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 matchFields:
                                                   description: A list of node selector
                                                     requirements by node's fields.
@@ -345,11 +349,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                               x-kubernetes-map-type: atomic
                                             weight:
@@ -363,6 +369,7 @@ spec:
                                           - weight
                                           type: object
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                       requiredDuringSchedulingIgnoredDuringExecution:
                                         description: |-
                                           If the affinity requirements specified by this field are not met at
@@ -408,11 +415,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 matchFields:
                                                   description: A list of node selector
                                                     requirements by node's fields.
@@ -441,14 +450,17 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                               x-kubernetes-map-type: atomic
                                             type: array
+                                            x-kubernetes-list-type: atomic
                                         required:
                                         - nodeSelectorTerms
                                         type: object
@@ -514,11 +526,13 @@ spec:
                                                             items:
                                                               type: string
                                                             type: array
+                                                            x-kubernetes-list-type: atomic
                                                         required:
                                                         - key
                                                         - operator
                                                         type: object
                                                       type: array
+                                                      x-kubernetes-list-type: atomic
                                                     matchLabels:
                                                       additionalProperties:
                                                         type: string
@@ -533,12 +547,12 @@ spec:
                                                   description: |-
                                                     MatchLabelKeys is a set of pod label keys to select which pods will
                                                     be taken into consideration. The keys are used to lookup values from the
-                                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                                     to select the group of existing pods which pods will be taken into consideration
                                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                     pod labels will be ignored. The default value is empty.
-                                                    The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                                    Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                                   items:
                                                     type: string
@@ -548,12 +562,12 @@ spec:
                                                   description: |-
                                                     MismatchLabelKeys is a set of pod label keys to select which pods will
                                                     be taken into consideration. The keys are used to lookup values from the
-                                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                                     to select the group of existing pods which pods will be taken into consideration
                                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                     pod labels will be ignored. The default value is empty.
-                                                    The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                                    Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                                   items:
                                                     type: string
@@ -596,11 +610,13 @@ spec:
                                                             items:
                                                               type: string
                                                             type: array
+                                                            x-kubernetes-list-type: atomic
                                                         required:
                                                         - key
                                                         - operator
                                                         type: object
                                                       type: array
+                                                      x-kubernetes-list-type: atomic
                                                     matchLabels:
                                                       additionalProperties:
                                                         type: string
@@ -620,6 +636,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 topologyKey:
                                                   description: |-
                                                     This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -642,6 +659,7 @@ spec:
                                           - weight
                                           type: object
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                       requiredDuringSchedulingIgnoredDuringExecution:
                                         description: |-
                                           If the affinity requirements specified by this field are not met at
@@ -693,11 +711,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 matchLabels:
                                                   additionalProperties:
                                                     type: string
@@ -712,12 +732,12 @@ spec:
                                               description: |-
                                                 MatchLabelKeys is a set of pod label keys to select which pods will
                                                 be taken into consideration. The keys are used to lookup values from the
-                                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                                 to select the group of existing pods which pods will be taken into consideration
                                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                 pod labels will be ignored. The default value is empty.
-                                                The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                                Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                                Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                               items:
                                                 type: string
@@ -727,12 +747,12 @@ spec:
                                               description: |-
                                                 MismatchLabelKeys is a set of pod label keys to select which pods will
                                                 be taken into consideration. The keys are used to lookup values from the
-                                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                                 to select the group of existing pods which pods will be taken into consideration
                                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                 pod labels will be ignored. The default value is empty.
-                                                The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                                Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                                Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                               items:
                                                 type: string
@@ -774,11 +794,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 matchLabels:
                                                   additionalProperties:
                                                     type: string
@@ -798,6 +820,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             topologyKey:
                                               description: |-
                                                 This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -810,6 +833,7 @@ spec:
                                           - topologyKey
                                           type: object
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     type: object
                                   podAntiAffinity:
                                     description: Describes pod anti-affinity scheduling
@@ -871,11 +895,13 @@ spec:
                                                             items:
                                                               type: string
                                                             type: array
+                                                            x-kubernetes-list-type: atomic
                                                         required:
                                                         - key
                                                         - operator
                                                         type: object
                                                       type: array
+                                                      x-kubernetes-list-type: atomic
                                                     matchLabels:
                                                       additionalProperties:
                                                         type: string
@@ -890,12 +916,12 @@ spec:
                                                   description: |-
                                                     MatchLabelKeys is a set of pod label keys to select which pods will
                                                     be taken into consideration. The keys are used to lookup values from the
-                                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                                     to select the group of existing pods which pods will be taken into consideration
                                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                     pod labels will be ignored. The default value is empty.
-                                                    The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                                    Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                                   items:
                                                     type: string
@@ -905,12 +931,12 @@ spec:
                                                   description: |-
                                                     MismatchLabelKeys is a set of pod label keys to select which pods will
                                                     be taken into consideration. The keys are used to lookup values from the
-                                                    incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                                     to select the group of existing pods which pods will be taken into consideration
                                                     for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                     pod labels will be ignored. The default value is empty.
-                                                    The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                                    Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                                     This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                                   items:
                                                     type: string
@@ -953,11 +979,13 @@ spec:
                                                             items:
                                                               type: string
                                                             type: array
+                                                            x-kubernetes-list-type: atomic
                                                         required:
                                                         - key
                                                         - operator
                                                         type: object
                                                       type: array
+                                                      x-kubernetes-list-type: atomic
                                                     matchLabels:
                                                       additionalProperties:
                                                         type: string
@@ -977,6 +1005,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 topologyKey:
                                                   description: |-
                                                     This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -999,6 +1028,7 @@ spec:
                                           - weight
                                           type: object
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                       requiredDuringSchedulingIgnoredDuringExecution:
                                         description: |-
                                           If the anti-affinity requirements specified by this field are not met at
@@ -1050,11 +1080,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 matchLabels:
                                                   additionalProperties:
                                                     type: string
@@ -1069,12 +1101,12 @@ spec:
                                               description: |-
                                                 MatchLabelKeys is a set of pod label keys to select which pods will
                                                 be taken into consideration. The keys are used to lookup values from the
-                                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)`
+                                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`
                                                 to select the group of existing pods which pods will be taken into consideration
                                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                 pod labels will be ignored. The default value is empty.
-                                                The same key is forbidden to exist in both MatchLabelKeys and LabelSelector.
-                                                Also, MatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                The same key is forbidden to exist in both matchLabelKeys and labelSelector.
+                                                Also, matchLabelKeys cannot be set when labelSelector isn't set.
                                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                               items:
                                                 type: string
@@ -1084,12 +1116,12 @@ spec:
                                               description: |-
                                                 MismatchLabelKeys is a set of pod label keys to select which pods will
                                                 be taken into consideration. The keys are used to lookup values from the
-                                                incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)`
+                                                incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`
                                                 to select the group of existing pods which pods will be taken into consideration
                                                 for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming
                                                 pod labels will be ignored. The default value is empty.
-                                                The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector.
-                                                Also, MismatchLabelKeys cannot be set when LabelSelector isn't set.
+                                                The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.
+                                                Also, mismatchLabelKeys cannot be set when labelSelector isn't set.
                                                 This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.
                                               items:
                                                 type: string
@@ -1131,11 +1163,13 @@ spec:
                                                         items:
                                                           type: string
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                     required:
                                                     - key
                                                     - operator
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 matchLabels:
                                                   additionalProperties:
                                                     type: string
@@ -1155,6 +1189,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             topologyKey:
                                               description: |-
                                                 This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@@ -1167,6 +1202,7 @@ spec:
                                           - topologyKey
                                           type: object
                                         type: array
+                                        x-kubernetes-list-type: atomic
                                     type: object
                                 type: object
                               automountServiceAccountToken:
@@ -1197,6 +1233,7 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     command:
                                       description: |-
                                         Entrypoint array. Not executed within a shell.
@@ -1210,6 +1247,7 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     env:
                                       description: |-
                                         List of environment variables to set in the container.
@@ -1334,6 +1372,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - name
+                                      x-kubernetes-list-type: map
                                     envFrom:
                                       description: |-
                                         List of sources to populate environment variables in the container.
@@ -1383,6 +1424,7 @@ spec:
                                             x-kubernetes-map-type: atomic
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     image:
                                       description: |-
                                         Container image name.
@@ -1424,6 +1466,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                             httpGet:
                                               description: HTTPGet specifies the http
@@ -1457,6 +1500,7 @@ spec:
                                                     - value
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 path:
                                                   description: Path to access on the
                                                     HTTP server.
@@ -1541,6 +1585,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                             httpGet:
                                               description: HTTPGet specifies the http
@@ -1574,6 +1619,7 @@ spec:
                                                     - value
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 path:
                                                   description: Path to access on the
                                                     HTTP server.
@@ -1654,6 +1700,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -1714,6 +1761,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -1873,6 +1921,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -1933,6 +1982,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -2130,6 +2180,30 @@ spec:
                                             2) has CAP_SYS_ADMIN
                                             Note that this field cannot be set when spec.os.name is windows.
                                           type: boolean
+                                        appArmorProfile:
+                                          description: |-
+                                            appArmorProfile is the AppArmor options to use by this container. If set, this profile
+                                            overrides the pod's appArmorProfile.
+                                            Note that this field cannot be set when spec.os.name is windows.
+                                          properties:
+                                            localhostProfile:
+                                              description: |-
+                                                localhostProfile indicates a profile loaded on the node that should be used.
+                                                The profile must be preconfigured on the node to work.
+                                                Must match the loaded name of the profile.
+                                                Must be set if and only if type is "Localhost".
+                                              type: string
+                                            type:
+                                              description: |-
+                                                type indicates which kind of AppArmor profile will be applied.
+                                                Valid options are:
+                                                  Localhost - a profile pre-loaded on the node.
+                                                  RuntimeDefault - the container runtime's default profile.
+                                                  Unconfined - no AppArmor enforcement.
+                                              type: string
+                                          required:
+                                          - type
+                                          type: object
                                         capabilities:
                                           description: |-
                                             The capabilities to add/drop when running containers.
@@ -2143,6 +2217,7 @@ spec:
                                                   POSIX capabilities type
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             drop:
                                               description: Removed capabilities
                                               items:
@@ -2150,6 +2225,7 @@ spec:
                                                   POSIX capabilities type
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         privileged:
                                           description: |-
@@ -2309,6 +2385,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -2369,6 +2446,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -2512,6 +2590,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - devicePath
+                                      x-kubernetes-list-type: map
                                     volumeMounts:
                                       description: |-
                                         Pod volumes to mount into the container's filesystem.
@@ -2531,6 +2612,8 @@ spec:
                                               to container and the other way around.
                                               When not set, MountPropagationNone is used.
                                               This field is beta in 1.10.
+                                              When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                                              (which defaults to None).
                                             type: string
                                           name:
                                             description: This must match the Name
@@ -2541,6 +2624,29 @@ spec:
                                               Mounted read-only if true, read-write otherwise (false or unspecified).
                                               Defaults to false.
                                             type: boolean
+                                          recursiveReadOnly:
+                                            description: |-
+                                              RecursiveReadOnly specifies whether read-only mounts should be handled
+                                              recursively.
+
+
+                                              If ReadOnly is false, this field has no meaning and must be unspecified.
+
+
+                                              If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                                              recursively read-only.  If this field is set to IfPossible, the mount is made
+                                              recursively read-only, if it is supported by the container runtime.  If this
+                                              field is set to Enabled, the mount is made recursively read-only if it is
+                                              supported by the container runtime, otherwise the pod will not be started and
+                                              an error will be generated to indicate the reason.
+
+
+                                              If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                                              None (or be unspecified, which defaults to None).
+
+
+                                              If this field is not specified, it is treated as an equivalent of Disabled.
+                                            type: string
                                           subPath:
                                             description: |-
                                               Path within the volume from which the container's volume should be mounted.
@@ -2558,6 +2664,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - mountPath
+                                      x-kubernetes-list-type: map
                                     workingDir:
                                       description: |-
                                         Container's working directory.
@@ -2569,6 +2678,9 @@ spec:
                                   - name
                                   type: object
                                 type: array
+                                x-kubernetes-list-map-keys:
+                                - name
+                                x-kubernetes-list-type: map
                               dnsConfig:
                                 description: |-
                                   Specifies the DNS parameters of a pod.
@@ -2583,6 +2695,7 @@ spec:
                                     items:
                                       type: string
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                   options:
                                     description: |-
                                       A list of DNS resolver options.
@@ -2600,6 +2713,7 @@ spec:
                                           type: string
                                       type: object
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                   searches:
                                     description: |-
                                       A list of DNS search domains for host-name lookup.
@@ -2608,6 +2722,7 @@ spec:
                                     items:
                                       type: string
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                 type: object
                               dnsPolicy:
                                 description: |-
@@ -2655,6 +2770,7 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     command:
                                       description: |-
                                         Entrypoint array. Not executed within a shell.
@@ -2668,6 +2784,7 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     env:
                                       description: |-
                                         List of environment variables to set in the container.
@@ -2792,6 +2909,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - name
+                                      x-kubernetes-list-type: map
                                     envFrom:
                                       description: |-
                                         List of sources to populate environment variables in the container.
@@ -2841,6 +2961,7 @@ spec:
                                             x-kubernetes-map-type: atomic
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     image:
                                       description: |-
                                         Container image name.
@@ -2879,6 +3000,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                             httpGet:
                                               description: HTTPGet specifies the http
@@ -2912,6 +3034,7 @@ spec:
                                                     - value
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 path:
                                                   description: Path to access on the
                                                     HTTP server.
@@ -2996,6 +3119,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                             httpGet:
                                               description: HTTPGet specifies the http
@@ -3029,6 +3153,7 @@ spec:
                                                     - value
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 path:
                                                   description: Path to access on the
                                                     HTTP server.
@@ -3106,6 +3231,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -3166,6 +3292,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -3315,6 +3442,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -3375,6 +3503,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -3559,6 +3688,30 @@ spec:
                                             2) has CAP_SYS_ADMIN
                                             Note that this field cannot be set when spec.os.name is windows.
                                           type: boolean
+                                        appArmorProfile:
+                                          description: |-
+                                            appArmorProfile is the AppArmor options to use by this container. If set, this profile
+                                            overrides the pod's appArmorProfile.
+                                            Note that this field cannot be set when spec.os.name is windows.
+                                          properties:
+                                            localhostProfile:
+                                              description: |-
+                                                localhostProfile indicates a profile loaded on the node that should be used.
+                                                The profile must be preconfigured on the node to work.
+                                                Must match the loaded name of the profile.
+                                                Must be set if and only if type is "Localhost".
+                                              type: string
+                                            type:
+                                              description: |-
+                                                type indicates which kind of AppArmor profile will be applied.
+                                                Valid options are:
+                                                  Localhost - a profile pre-loaded on the node.
+                                                  RuntimeDefault - the container runtime's default profile.
+                                                  Unconfined - no AppArmor enforcement.
+                                              type: string
+                                          required:
+                                          - type
+                                          type: object
                                         capabilities:
                                           description: |-
                                             The capabilities to add/drop when running containers.
@@ -3572,6 +3725,7 @@ spec:
                                                   POSIX capabilities type
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             drop:
                                               description: Removed capabilities
                                               items:
@@ -3579,6 +3733,7 @@ spec:
                                                   POSIX capabilities type
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         privileged:
                                           description: |-
@@ -3732,6 +3887,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -3792,6 +3948,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -3945,6 +4102,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - devicePath
+                                      x-kubernetes-list-type: map
                                     volumeMounts:
                                       description: |-
                                         Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers.
@@ -3964,6 +4124,8 @@ spec:
                                               to container and the other way around.
                                               When not set, MountPropagationNone is used.
                                               This field is beta in 1.10.
+                                              When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                                              (which defaults to None).
                                             type: string
                                           name:
                                             description: This must match the Name
@@ -3974,6 +4136,29 @@ spec:
                                               Mounted read-only if true, read-write otherwise (false or unspecified).
                                               Defaults to false.
                                             type: boolean
+                                          recursiveReadOnly:
+                                            description: |-
+                                              RecursiveReadOnly specifies whether read-only mounts should be handled
+                                              recursively.
+
+
+                                              If ReadOnly is false, this field has no meaning and must be unspecified.
+
+
+                                              If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                                              recursively read-only.  If this field is set to IfPossible, the mount is made
+                                              recursively read-only, if it is supported by the container runtime.  If this
+                                              field is set to Enabled, the mount is made recursively read-only if it is
+                                              supported by the container runtime, otherwise the pod will not be started and
+                                              an error will be generated to indicate the reason.
+
+
+                                              If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                                              None (or be unspecified, which defaults to None).
+
+
+                                              If this field is not specified, it is treated as an equivalent of Disabled.
+                                            type: string
                                           subPath:
                                             description: |-
                                               Path within the volume from which the container's volume should be mounted.
@@ -3991,6 +4176,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - mountPath
+                                      x-kubernetes-list-type: map
                                     workingDir:
                                       description: |-
                                         Container's working directory.
@@ -4002,10 +4190,13 @@ spec:
                                   - name
                                   type: object
                                 type: array
+                                x-kubernetes-list-map-keys:
+                                - name
+                                x-kubernetes-list-type: map
                               hostAliases:
                                 description: |-
                                   HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts
-                                  file if specified. This is only valid for non-hostNetwork pods.
+                                  file if specified.
                                 items:
                                   description: |-
                                     HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
@@ -4016,11 +4207,17 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     ip:
                                       description: IP address of the host file entry.
                                       type: string
+                                  required:
+                                  - ip
                                   type: object
                                 type: array
+                                x-kubernetes-list-map-keys:
+                                - ip
+                                x-kubernetes-list-type: map
                               hostIPC:
                                 description: |-
                                   Use the host's ipc namespace.
@@ -4070,9 +4267,14 @@ spec:
                                         More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
                                         TODO: Add other useful fields. apiVersion, kind, uid?
                                       type: string
+                                  required:
+                                  - name
                                   type: object
                                   x-kubernetes-map-type: atomic
                                 type: array
+                                x-kubernetes-list-map-keys:
+                                - name
+                                x-kubernetes-list-type: map
                               initContainers:
                                 description: |-
                                   List of initialization containers belonging to the pod.
@@ -4105,6 +4307,7 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     command:
                                       description: |-
                                         Entrypoint array. Not executed within a shell.
@@ -4118,6 +4321,7 @@ spec:
                                       items:
                                         type: string
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     env:
                                       description: |-
                                         List of environment variables to set in the container.
@@ -4242,6 +4446,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - name
+                                      x-kubernetes-list-type: map
                                     envFrom:
                                       description: |-
                                         List of sources to populate environment variables in the container.
@@ -4291,6 +4498,7 @@ spec:
                                             x-kubernetes-map-type: atomic
                                         type: object
                                       type: array
+                                      x-kubernetes-list-type: atomic
                                     image:
                                       description: |-
                                         Container image name.
@@ -4332,6 +4540,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                             httpGet:
                                               description: HTTPGet specifies the http
@@ -4365,6 +4574,7 @@ spec:
                                                     - value
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 path:
                                                   description: Path to access on the
                                                     HTTP server.
@@ -4449,6 +4659,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                               type: object
                                             httpGet:
                                               description: HTTPGet specifies the http
@@ -4482,6 +4693,7 @@ spec:
                                                     - value
                                                     type: object
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 path:
                                                   description: Path to access on the
                                                     HTTP server.
@@ -4562,6 +4774,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -4622,6 +4835,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -4781,6 +4995,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -4841,6 +5056,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -5038,6 +5254,30 @@ spec:
                                             2) has CAP_SYS_ADMIN
                                             Note that this field cannot be set when spec.os.name is windows.
                                           type: boolean
+                                        appArmorProfile:
+                                          description: |-
+                                            appArmorProfile is the AppArmor options to use by this container. If set, this profile
+                                            overrides the pod's appArmorProfile.
+                                            Note that this field cannot be set when spec.os.name is windows.
+                                          properties:
+                                            localhostProfile:
+                                              description: |-
+                                                localhostProfile indicates a profile loaded on the node that should be used.
+                                                The profile must be preconfigured on the node to work.
+                                                Must match the loaded name of the profile.
+                                                Must be set if and only if type is "Localhost".
+                                              type: string
+                                            type:
+                                              description: |-
+                                                type indicates which kind of AppArmor profile will be applied.
+                                                Valid options are:
+                                                  Localhost - a profile pre-loaded on the node.
+                                                  RuntimeDefault - the container runtime's default profile.
+                                                  Unconfined - no AppArmor enforcement.
+                                              type: string
+                                          required:
+                                          - type
+                                          type: object
                                         capabilities:
                                           description: |-
                                             The capabilities to add/drop when running containers.
@@ -5051,6 +5291,7 @@ spec:
                                                   POSIX capabilities type
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             drop:
                                               description: Removed capabilities
                                               items:
@@ -5058,6 +5299,7 @@ spec:
                                                   POSIX capabilities type
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         privileged:
                                           description: |-
@@ -5217,6 +5459,7 @@ spec:
                                               items:
                                                 type: string
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                           type: object
                                         failureThreshold:
                                           description: |-
@@ -5277,6 +5520,7 @@ spec:
                                                 - value
                                                 type: object
                                               type: array
+                                              x-kubernetes-list-type: atomic
                                             path:
                                               description: Path to access on the HTTP
                                                 server.
@@ -5420,6 +5664,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - devicePath
+                                      x-kubernetes-list-type: map
                                     volumeMounts:
                                       description: |-
                                         Pod volumes to mount into the container's filesystem.
@@ -5439,6 +5686,8 @@ spec:
                                               to container and the other way around.
                                               When not set, MountPropagationNone is used.
                                               This field is beta in 1.10.
+                                              When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                                              (which defaults to None).
                                             type: string
                                           name:
                                             description: This must match the Name
@@ -5449,6 +5698,29 @@ spec:
                                               Mounted read-only if true, read-write otherwise (false or unspecified).
                                               Defaults to false.
                                             type: boolean
+                                          recursiveReadOnly:
+                                            description: |-
+                                              RecursiveReadOnly specifies whether read-only mounts should be handled
+                                              recursively.
+
+
+                                              If ReadOnly is false, this field has no meaning and must be unspecified.
+
+
+                                              If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                                              recursively read-only.  If this field is set to IfPossible, the mount is made
+                                              recursively read-only, if it is supported by the container runtime.  If this
+                                              field is set to Enabled, the mount is made recursively read-only if it is
+                                              supported by the container runtime, otherwise the pod will not be started and
+                                              an error will be generated to indicate the reason.
+
+
+                                              If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                                              None (or be unspecified, which defaults to None).
+
+
+                                              If this field is not specified, it is treated as an equivalent of Disabled.
+                                            type: string
                                           subPath:
                                             description: |-
                                               Path within the volume from which the container's volume should be mounted.
@@ -5466,6 +5738,9 @@ spec:
                                         - name
                                         type: object
                                       type: array
+                                      x-kubernetes-list-map-keys:
+                                      - mountPath
+                                      x-kubernetes-list-type: map
                                     workingDir:
                                       description: |-
                                         Container's working directory.
@@ -5477,6 +5752,9 @@ spec:
                                   - name
                                   type: object
                                 type: array
+                                x-kubernetes-list-map-keys:
+                                - name
+                                x-kubernetes-list-type: map
                               nodeName:
                                 description: |-
                                   NodeName is a request to schedule this pod onto a specific node. If it is non-empty,
@@ -5506,6 +5784,7 @@ spec:
                                   - spec.hostPID
                                   - spec.hostIPC
                                   - spec.hostUsers
+                                  - spec.securityContext.appArmorProfile
                                   - spec.securityContext.seLinuxOptions
                                   - spec.securityContext.seccompProfile
                                   - spec.securityContext.fsGroup
@@ -5515,6 +5794,7 @@ spec:
                                   - spec.securityContext.runAsUser
                                   - spec.securityContext.runAsGroup
                                   - spec.securityContext.supplementalGroups
+                                  - spec.containers[*].securityContext.appArmorProfile
                                   - spec.containers[*].securityContext.seLinuxOptions
                                   - spec.containers[*].securityContext.seccompProfile
                                   - spec.containers[*].securityContext.capabilities
@@ -5594,6 +5874,7 @@ spec:
                                   - conditionType
                                   type: object
                                 type: array
+                                x-kubernetes-list-type: atomic
                               resourceClaims:
                                 description: |-
                                   ResourceClaims defines which ResourceClaims must be allocated
@@ -5680,9 +5961,6 @@ spec:
 
 
                                   SchedulingGates can only be set at pod creation time, and be removed only afterwards.
-
-
-                                  This is a beta feature enabled by the PodSchedulingReadiness feature gate.
                                 items:
                                   description: PodSchedulingGate is associated to
                                     a Pod to guard its scheduling.
@@ -5704,6 +5982,29 @@ spec:
                                   SecurityContext holds pod-level security attributes and common container settings.
                                   Optional: Defaults to empty.  See type description for default values of each field.
                                 properties:
+                                  appArmorProfile:
+                                    description: |-
+                                      appArmorProfile is the AppArmor options to use by the containers in this pod.
+                                      Note that this field cannot be set when spec.os.name is windows.
+                                    properties:
+                                      localhostProfile:
+                                        description: |-
+                                          localhostProfile indicates a profile loaded on the node that should be used.
+                                          The profile must be preconfigured on the node to work.
+                                          Must match the loaded name of the profile.
+                                          Must be set if and only if type is "Localhost".
+                                        type: string
+                                      type:
+                                        description: |-
+                                          type indicates which kind of AppArmor profile will be applied.
+                                          Valid options are:
+                                            Localhost - a profile pre-loaded on the node.
+                                            RuntimeDefault - the container runtime's default profile.
+                                            Unconfined - no AppArmor enforcement.
+                                        type: string
+                                    required:
+                                    - type
+                                    type: object
                                   fsGroup:
                                     description: |-
                                       A special supplemental group that applies to all containers in a pod.
@@ -5823,6 +6124,7 @@ spec:
                                       format: int64
                                       type: integer
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                   sysctls:
                                     description: |-
                                       Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
@@ -5843,6 +6145,7 @@ spec:
                                       - value
                                       type: object
                                     type: array
+                                    x-kubernetes-list-type: atomic
                                   windowsOptions:
                                     description: |-
                                       The Windows specific settings applied to all containers.
@@ -5878,7 +6181,7 @@ spec:
                                 type: object
                               serviceAccount:
                                 description: |-
-                                  DeprecatedServiceAccount is a depreciated alias for ServiceAccountName.
+                                  DeprecatedServiceAccount is a deprecated alias for ServiceAccountName.
                                   Deprecated: Use serviceAccountName instead.
                                 type: string
                               serviceAccountName:
@@ -5958,6 +6261,7 @@ spec:
                                       type: string
                                   type: object
                                 type: array
+                                x-kubernetes-list-type: atomic
                               topologySpreadConstraints:
                                 description: |-
                                   TopologySpreadConstraints describes how a group of pods ought to spread across topology
@@ -6000,11 +6304,13 @@ spec:
                                                 items:
                                                   type: string
                                                 type: array
+                                                x-kubernetes-list-type: atomic
                                             required:
                                             - key
                                             - operator
                                             type: object
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         matchLabels:
                                           additionalProperties:
                                             type: string
@@ -6075,9 +6381,6 @@ spec:
                                         In this situation, new pod with the same labelSelector cannot be scheduled,
                                         because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones,
                                         it will violate MaxSkew.
-
-
-                                        This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default).
                                       format: int32
                                       type: integer
                                     nodeAffinityPolicy:
@@ -6264,6 +6567,7 @@ spec:
                                           items:
                                             type: string
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         path:
                                           description: 'path is Optional: Used as
                                             the mounted root, rather than the full
@@ -6393,6 +6697,7 @@ spec:
                                             - path
                                             type: object
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         name:
                                           description: |-
                                             Name of the referent.
@@ -6480,7 +6785,8 @@ spec:
                                               fieldRef:
                                                 description: 'Required: Selects a
                                                   field of the pod: only annotations,
-                                                  labels, name and namespace are supported.'
+                                                  labels, name, namespace and uid
+                                                  are supported.'
                                                 properties:
                                                   apiVersion:
                                                     description: Version of the schema
@@ -6546,6 +6852,7 @@ spec:
                                             - path
                                             type: object
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                       type: object
                                     emptyDir:
                                       description: |-
@@ -6668,6 +6975,7 @@ spec:
                                                   items:
                                                     type: string
                                                   type: array
+                                                  x-kubernetes-list-type: atomic
                                                 dataSource:
                                                   description: |-
                                                     dataSource field can be used to specify either:
@@ -6815,11 +7123,13 @@ spec:
                                                             items:
                                                               type: string
                                                             type: array
+                                                            x-kubernetes-list-type: atomic
                                                         required:
                                                         - key
                                                         - operator
                                                         type: object
                                                       type: array
+                                                      x-kubernetes-list-type: atomic
                                                     matchLabels:
                                                       additionalProperties:
                                                         type: string
@@ -6847,7 +7157,7 @@ spec:
                                                     If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be
                                                     set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource
                                                     exists.
-                                                    More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass
+                                                    More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/
                                                     (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled.
                                                   type: string
                                                 volumeMode:
@@ -6893,6 +7203,7 @@ spec:
                                           items:
                                             type: string
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         wwids:
                                           description: |-
                                             wwids Optional: FC volume world wide identifiers (wwids)
@@ -6900,6 +7211,7 @@ spec:
                                           items:
                                             type: string
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                       type: object
                                     flexVolume:
                                       description: |-
@@ -7124,6 +7436,7 @@ spec:
                                           items:
                                             type: string
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         readOnly:
                                           description: |-
                                             readOnly here will force the ReadOnly setting in VolumeMounts.
@@ -7320,11 +7633,13 @@ spec:
                                                               items:
                                                                 type: string
                                                               type: array
+                                                              x-kubernetes-list-type: atomic
                                                           required:
                                                           - key
                                                           - operator
                                                           type: object
                                                         type: array
+                                                        x-kubernetes-list-type: atomic
                                                       matchLabels:
                                                         additionalProperties:
                                                           type: string
@@ -7405,6 +7720,7 @@ spec:
                                                       - path
                                                       type: object
                                                     type: array
+                                                    x-kubernetes-list-type: atomic
                                                   name:
                                                     description: |-
                                                       Name of the referent.
@@ -7435,8 +7751,8 @@ spec:
                                                           description: 'Required:
                                                             Selects a field of the
                                                             pod: only annotations,
-                                                            labels, name and namespace
-                                                            are supported.'
+                                                            labels, name, namespace
+                                                            and uid are supported.'
                                                           properties:
                                                             apiVersion:
                                                               description: Version
@@ -7510,6 +7826,7 @@ spec:
                                                       - path
                                                       type: object
                                                     type: array
+                                                    x-kubernetes-list-type: atomic
                                                 type: object
                                               secret:
                                                 description: secret information about
@@ -7554,6 +7871,7 @@ spec:
                                                       - path
                                                       type: object
                                                     type: array
+                                                    x-kubernetes-list-type: atomic
                                                   name:
                                                     description: |-
                                                       Name of the referent.
@@ -7599,6 +7917,7 @@ spec:
                                                 type: object
                                             type: object
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                       type: object
                                     quobyte:
                                       description: quobyte represents a Quobyte mount
@@ -7669,6 +7988,7 @@ spec:
                                           items:
                                             type: string
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         pool:
                                           description: |-
                                             pool is the rados pool name.
@@ -7828,6 +8148,7 @@ spec:
                                             - path
                                             type: object
                                           type: array
+                                          x-kubernetes-list-type: atomic
                                         optional:
                                           description: optional field specify whether
                                             the Secret or its keys must be defined
@@ -7914,6 +8235,9 @@ spec:
                                   - name
                                   type: object
                                 type: array
+                                x-kubernetes-list-map-keys:
+                                - name
+                                x-kubernetes-list-type: map
                             required:
                             - containers
                             type: object
diff --git a/cluster/crds/pkg.crossplane.io_functionrevisions.yaml b/cluster/crds/pkg.crossplane.io_functionrevisions.yaml
index 7fb151816..d7ba776e2 100644
--- a/cluster/crds/pkg.crossplane.io_functionrevisions.yaml
+++ b/cluster/crds/pkg.crossplane.io_functionrevisions.yaml
@@ -290,6 +290,7 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     nonResourceURLs:
                       description: |-
                         NonResourceURLs is a set of partial urls that a user should have access to.  *s are allowed, but only as the full, final step in the path
@@ -298,6 +299,7 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     resourceNames:
                       description: ResourceNames is an optional white list of names
                         that the rule applies to.  An empty set means that everything
@@ -305,18 +307,21 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     resources:
                       description: Resources is a list of resources this rule applies
                         to. '*' represents all resources.
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     verbs:
                       description: Verbs is a list of Verbs that apply to ALL the
                         ResourceKinds contained in this rule. '*' represents all verbs.
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                   required:
                   - verbs
                   type: object
diff --git a/cluster/crds/pkg.crossplane.io_providerrevisions.yaml b/cluster/crds/pkg.crossplane.io_providerrevisions.yaml
index 961bfc454..439140a4c 100644
--- a/cluster/crds/pkg.crossplane.io_providerrevisions.yaml
+++ b/cluster/crds/pkg.crossplane.io_providerrevisions.yaml
@@ -285,6 +285,7 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     nonResourceURLs:
                       description: |-
                         NonResourceURLs is a set of partial urls that a user should have access to.  *s are allowed, but only as the full, final step in the path
@@ -293,6 +294,7 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     resourceNames:
                       description: ResourceNames is an optional white list of names
                         that the rule applies to.  An empty set means that everything
@@ -300,18 +302,21 @@ spec:
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     resources:
                       description: Resources is a list of resources this rule applies
                         to. '*' represents all resources.
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                     verbs:
                       description: Verbs is a list of Verbs that apply to ALL the
                         ResourceKinds contained in this rule. '*' represents all verbs.
                       items:
                         type: string
                       type: array
+                      x-kubernetes-list-type: atomic
                   required:
                   - verbs
                   type: object
diff --git a/cluster/meta/meta.pkg.crossplane.io_providers.yaml b/cluster/meta/meta.pkg.crossplane.io_providers.yaml
index 69bf74a19..b7997e5a9 100644
--- a/cluster/meta/meta.pkg.crossplane.io_providers.yaml
+++ b/cluster/meta/meta.pkg.crossplane.io_providers.yaml
@@ -62,6 +62,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         nonResourceURLs:
                           description: |-
                             NonResourceURLs is a set of partial urls that a user should have access to.  *s are allowed, but only as the full, final step in the path
@@ -70,6 +71,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         resourceNames:
                           description: ResourceNames is an optional white list of
                             names that the rule applies to.  An empty set means that
@@ -77,12 +79,14 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         resources:
                           description: Resources is a list of resources this rule
                             applies to. '*' represents all resources.
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         verbs:
                           description: Verbs is a list of Verbs that apply to ALL
                             the ResourceKinds contained in this rule. '*' represents
@@ -90,6 +94,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                       required:
                       - verbs
                       type: object
@@ -186,6 +191,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         nonResourceURLs:
                           description: |-
                             NonResourceURLs is a set of partial urls that a user should have access to.  *s are allowed, but only as the full, final step in the path
@@ -194,6 +200,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         resourceNames:
                           description: ResourceNames is an optional white list of
                             names that the rule applies to.  An empty set means that
@@ -201,12 +208,14 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         resources:
                           description: Resources is a list of resources this rule
                             applies to. '*' represents all resources.
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                         verbs:
                           description: Verbs is a list of Verbs that apply to ALL
                             the ResourceKinds contained in this rule. '*' represents
@@ -214,6 +223,7 @@ spec:
                           items:
                             type: string
                           type: array
+                          x-kubernetes-list-type: atomic
                       required:
                       - verbs
                       type: object
diff --git a/cmd/crank/beta/render/render.go b/cmd/crank/beta/render/render.go
index 37268302e..c03e4d581 100644
--- a/cmd/crank/beta/render/render.go
+++ b/cmd/crank/beta/render/render.go
@@ -335,7 +335,6 @@ func filterExtraResources(ers []unstructured.Unstructured, selector *fnv1beta1.R
 	}
 	out := &fnv1beta1.Resources{}
 	for _, er := range ers {
-		er := er
 		if selector.GetApiVersion() != er.GetAPIVersion() {
 			continue
 		}
diff --git a/cmd/crank/xpkg/push.go b/cmd/crank/xpkg/push.go
index a97832f40..1aceab488 100644
--- a/cmd/crank/xpkg/push.go
+++ b/cmd/crank/xpkg/push.go
@@ -152,7 +152,6 @@ func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocognit // This f
 	adds := make([]mutate.IndexAddendum, len(c.PackageFiles))
 	g, ctx := errgroup.WithContext(context.Background())
 	for i, file := range c.PackageFiles {
-		i, file := i, file // Pin range variables for use in goroutine
 		g.Go(func() error {
 			img, err := tarball.ImageFromPath(filepath.Clean(file), nil)
 			if err != nil {
diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go
index 0a5545fed..7ad9b444d 100644
--- a/cmd/crossplane/core/core.go
+++ b/cmd/crossplane/core/core.go
@@ -21,6 +21,7 @@ import (
 	"context"
 	"crypto/tls"
 	"fmt"
+	"io"
 	"os"
 	"path/filepath"
 	"time"
@@ -30,6 +31,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/rest"
+	kcache "k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/leaderelection/resourcelock"
 	"k8s.io/client-go/tools/record"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -44,11 +46,13 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/feature"
 	"github.com/crossplane/crossplane-runtime/pkg/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 
 	"github.com/crossplane/crossplane/internal/controller/apiextensions"
 	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
 	"github.com/crossplane/crossplane/internal/controller/pkg"
 	pkgcontroller "github.com/crossplane/crossplane/internal/controller/pkg/controller"
+	"github.com/crossplane/crossplane/internal/engine"
 	"github.com/crossplane/crossplane/internal/features"
 	"github.com/crossplane/crossplane/internal/initializer"
 	"github.com/crossplane/crossplane/internal/metrics"
@@ -134,6 +138,8 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 		Deduplicate: true,
 	})
 
+	// The claim and XR controllers don't use the manager's cache or client.
+	// They use their own. They're setup later in this method.
 	eb := record.NewBroadcaster()
 	mgr, err := ctrl.NewManager(ratelimiter.LimitRESTConfig(cfg, c.MaxReconcileRate), ctrl.Options{
 		Scheme: s,
@@ -270,9 +276,91 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 		log.Info("Alpha feature enabled", "flag", features.EnableAlphaClaimSSA)
 	}
 
+	// Claim and XR controllers are started and stopped dynamically by the
+	// ControllerEngine below. When realtime compositions are enabled, they also
+	// start and stop their watches (e.g. of composed resources) dynamically. To
+	// do this, the ControllerEngine must have exclusive ownership of a cache.
+	// This allows it to track what controllers are using the cache's informers.
+	ca, err := cache.New(mgr.GetConfig(), cache.Options{
+		HTTPClient: mgr.GetHTTPClient(),
+		Scheme:     mgr.GetScheme(),
+		Mapper:     mgr.GetRESTMapper(),
+		SyncPeriod: &c.SyncInterval,
+
+		// When a CRD is deleted, any informers for its GVKs will start trying
+		// to restart their watches, and fail with scary errors. This should
+		// only happen when realtime composition is enabled, and we should GC
+		// the informer within 60 seconds. This handler tries to make the error
+		// a little more informative, and less scary.
+		DefaultWatchErrorHandler: func(_ *kcache.Reflector, err error) {
+			if errors.Is(io.EOF, err) {
+				// Watch closed normally.
+				return
+			}
+			log.Debug("Watch error - probably due to CRD being uninstalled", "error", err)
+		},
+	})
+	if err != nil {
+		return errors.Wrap(err, "cannot create cache for API extension controllers")
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	go func() {
+		// Don't start the cache until the manager is elected.
+		<-mgr.Elected()
+
+		if err := ca.Start(ctx); err != nil {
+			log.Info("API extensions cache returned an error", "error", err)
+		}
+
+		log.Info("API extensions cache stopped")
+	}()
+
+	cl, err := client.New(mgr.GetConfig(), client.Options{
+		HTTPClient: mgr.GetHTTPClient(),
+		Scheme:     mgr.GetScheme(),
+		Mapper:     mgr.GetRESTMapper(),
+		Cache: &client.CacheOptions{
+			Reader: ca,
+
+			// Don't cache secrets - there may be a lot of them.
+			DisableFor: []client.Object{&corev1.Secret{}},
+
+			// Cache unstructured resources (like XRs and MRs) on Get and List.
+			Unstructured: true,
+		},
+	})
+	if err != nil {
+		return errors.Wrap(err, "cannot create client for API extension controllers")
+	}
+
+	// It's important the engine's client is wrapped with unstructured.NewClient
+	// because controller-runtime always caches *unstructured.Unstructured, not
+	// our wrapper types like *composite.Unstructured. This client takes care of
+	// automatically wrapping and unwrapping *unstructured.Unstructured.
+	ce := engine.New(mgr,
+		engine.TrackInformers(ca, mgr.GetScheme()),
+		unstructured.NewClient(cl),
+		engine.WithLogger(log),
+	)
+
+	// TODO(negz): Garbage collect informers for CRs that are still defined
+	// (i.e. still have CRDs) but aren't used? Currently if an XR starts
+	// composing a kind of CR then stops, we won't stop the unused informer
+	// until the CRD that defines the CR is deleted. That could never happen.
+	// Consider for example composing two types of MR from the same provider,
+	// then updating to compose only one.
+
+	// Garbage collect informers for custom resources when their CRD is deleted.
+	if err := ce.GarbageCollectCustomResourceInformers(ctx); err != nil {
+		return errors.Wrap(err, "cannot start garbage collector for custom resource informers")
+	}
+
 	ao := apiextensionscontroller.Options{
-		Options:        o,
-		FunctionRunner: functionRunner,
+		Options:          o,
+		ControllerEngine: ce,
+		FunctionRunner:   functionRunner,
 	}
 
 	if err := apiextensions.Setup(mgr, ao); err != nil {
diff --git a/go.mod b/go.mod
index 228ae8752..002770eb5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/crossplane/crossplane
 
-go 1.21
+go 1.22.0
 
 toolchain go1.22.3
 
@@ -9,7 +9,7 @@ require (
 	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
 	github.com/Masterminds/semver v1.5.0
 	github.com/alecthomas/kong v0.8.1
-	github.com/crossplane/crossplane-runtime v1.17.0-rc.0
+	github.com/crossplane/crossplane-runtime v1.17.0-rc.0.0.20240520203451-fc036618ffd8
 	github.com/docker/docker v25.0.5+incompatible
 	github.com/docker/go-connections v0.5.0
 	github.com/emicklei/dot v1.6.2
@@ -24,21 +24,21 @@ require (
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/afero v1.11.0
 	github.com/upbound/up-sdk-go v0.1.1-0.20240122203953-2d00664aab8e
-	golang.org/x/sync v0.6.0
-	google.golang.org/grpc v1.61.0
+	golang.org/x/sync v0.7.0
+	google.golang.org/grpc v1.63.2
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
 	google.golang.org/protobuf v1.33.0
-	k8s.io/api v0.29.1
-	k8s.io/apiextensions-apiserver v0.29.1
-	k8s.io/apimachinery v0.29.1
-	k8s.io/apiserver v0.29.1
+	k8s.io/api v0.30.0
+	k8s.io/apiextensions-apiserver v0.30.0
+	k8s.io/apimachinery v0.30.0
+	k8s.io/apiserver v0.30.0
 	k8s.io/cli-runtime v0.29.1
-	k8s.io/client-go v0.29.1
-	k8s.io/code-generator v0.29.1
+	k8s.io/client-go v0.30.0
+	k8s.io/code-generator v0.30.0
 	k8s.io/kubectl v0.29.1
 	k8s.io/metrics v0.29.1
 	k8s.io/utils v0.0.0-20230726121419-3b25d923346b
-	sigs.k8s.io/controller-runtime v0.17.0
+	sigs.k8s.io/controller-runtime v0.18.2
 	sigs.k8s.io/controller-tools v0.14.0
 	sigs.k8s.io/e2e-framework v0.3.0
 	sigs.k8s.io/kind v0.20.0
@@ -53,6 +53,7 @@ require (
 	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/chai2010/gettext-go v1.0.2 // indirect
 	github.com/cloudflare/circl v1.3.7 // indirect
+	github.com/containerd/log v0.1.0 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
 	github.com/distribution/reference v0.5.0 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
@@ -62,7 +63,7 @@ require (
 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
 	github.com/google/btree v1.0.1 // indirect
-	github.com/google/cel-go v0.17.7 // indirect
+	github.com/google/cel-go v0.17.8 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
@@ -81,17 +82,20 @@ require (
 	github.com/stoewer/go-strcase v1.3.0 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.26.0 // indirect
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
-	golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
+	golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
+	k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect
 	sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
 	sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
 )
 
 require (
-	cloud.google.com/go/compute v1.23.3 // indirect
+	cloud.google.com/go/compute v1.24.0 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
@@ -132,10 +136,10 @@ require (
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
-	github.com/evanphx/json-patch/v5 v5.8.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.9.0 // indirect
 	github.com/fatih/color v1.16.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
-	github.com/go-logr/logr v1.4.1
+	github.com/go-logr/logr v1.4.1 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-logr/zapr v1.3.0 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
@@ -144,10 +148,10 @@ require (
 	github.com/gobuffalo/flect v1.0.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
-	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230516205744-dbecb1de8cfa // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/uuid v1.6.0
+	github.com/google/uuid v1.6.0 // indirect
 	github.com/imdario/mergo v0.3.16 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -174,30 +178,29 @@ require (
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/vbatts/tar-split v0.11.5 // indirect
 	github.com/vladimirvivien/gexe v0.2.0 // indirect
-	go.opentelemetry.io/otel v1.19.0 // indirect
-	go.opentelemetry.io/otel/metric v1.19.0 // indirect
-	go.opentelemetry.io/otel/trace v1.19.0 // indirect
+	go.opentelemetry.io/otel v1.26.0 // indirect
+	go.opentelemetry.io/otel/metric v1.26.0 // indirect
+	go.opentelemetry.io/otel/trace v1.26.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	go.uber.org/zap v1.26.0 // indirect
-	golang.org/x/crypto v0.21.0 // indirect
-	golang.org/x/mod v0.14.0 // indirect
-	golang.org/x/net v0.23.0
-	golang.org/x/oauth2 v0.15.0 // indirect
-	golang.org/x/sys v0.18.0 // indirect
-	golang.org/x/term v0.18.0
+	go.uber.org/zap v1.27.0 // indirect
+	golang.org/x/crypto v0.22.0 // indirect
+	golang.org/x/mod v0.17.0 // indirect
+	golang.org/x/net v0.24.0
+	golang.org/x/oauth2 v0.17.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/term v0.19.0
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
-	golang.org/x/tools v0.17.0 // indirect
+	golang.org/x/tools v0.20.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/appengine v1.6.8 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/component-base v0.29.1 // indirect
-	k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
-	k8s.io/klog/v2 v2.110.1
-	k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
+	k8s.io/component-base v0.30.0 // indirect
+	k8s.io/klog/v2 v2.120.1
+	k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 258458178..3ed062774 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,6 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
-cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
+cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
+cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
@@ -96,8 +96,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
 github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
 github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
 github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
-github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
-github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -125,8 +125,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/crossplane/crossplane-runtime v1.17.0-rc.0 h1:v+JZ+94bQhunadP3wM64Mw6OnpPTwmiZRrShZEUQoMI=
-github.com/crossplane/crossplane-runtime v1.17.0-rc.0/go.mod h1:Pz2tdGVMF6KDGzHZOkvKro0nKc8EzK0sb/nSA7pH4Dc=
+github.com/crossplane/crossplane-runtime v1.17.0-rc.0.0.20240520203451-fc036618ffd8 h1:4OtdWor2ixE1Nk+96//Knf63wRiyKFnxFcJx4iM+WY0=
+github.com/crossplane/crossplane-runtime v1.17.0-rc.0.0.20240520203451-fc036618ffd8/go.mod h1:aZ1ODIvtOPFFVZ9oo3qVH/MQHuim24qWg2Tj6n+AIf4=
 github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
 github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
 github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
@@ -172,8 +172,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
 github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
-github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro=
-github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
+github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
+github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
 github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
@@ -194,9 +194,7 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
 github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
 github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
 github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -235,12 +233,12 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
 github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
-github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ=
-github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
+github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto=
+github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
 github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -261,11 +259,10 @@ github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230919002926-
 github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230516205744-dbecb1de8cfa h1:+MG+Q2Q7mtW6kCIbUPZ9ZMrj7xOWDKI1hhy1qp0ygI0=
 github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230516205744-dbecb1de8cfa/go.mod h1:KdL98/Va8Dy1irB6lTxIRIQ7bQj4lbrlvqUzKEQ+ZBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
-github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
+github.com/google/pprof v0.0.0-20240422182052-72c8669ad3e7 h1:3q13T5NW3mlTJZM6B5UAsf2N5NYFbYWIyI3W8DlvBDU=
+github.com/google/pprof v0.0.0-20240422182052-72c8669ad3e7/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
 github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -279,8 +276,8 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
 github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
@@ -308,7 +305,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
 github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -354,10 +350,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
-github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
-github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
-github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
+github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
+github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
+github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
@@ -414,8 +410,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/upbound/up-sdk-go v0.1.1-0.20240122203953-2d00664aab8e h1:aNzUuv4ZKH2OT3Qv6dpZxkMPDOfl/6MoS79T/zUzako=
 github.com/upbound/up-sdk-go v0.1.1-0.20240122203953-2d00664aab8e/go.mod h1:IDIbYDb9fbedtxCc2CrdGcVRol6la7z2gkKh0VYWVGk=
 github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
@@ -438,32 +434,32 @@ go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao
 go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 h1:ZOLJc06r4CB42laIXg/7udr0pbZyuAihN10A/XuiQRY=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
-go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
-go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
+go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
+go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
-go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
-go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
-go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
-go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
-go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
-go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
-go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
-go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38=
+go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
+go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
+go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
+go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
+go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
+go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
+go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
+go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
-go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -474,11 +470,11 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
-golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
+golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
+golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -488,8 +484,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
-golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -506,11 +502,11 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
-golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -519,8 +515,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -544,8 +540,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -553,8 +549,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -574,15 +570,14 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
-golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
+golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
+golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -596,17 +591,17 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
-google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
-google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
-google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
-google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
+google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
+google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -644,39 +639,38 @@ gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
 gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw=
-k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ=
-k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw=
-k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU=
-k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc=
-k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
-k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g=
-k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw=
+k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
+k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
+k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
+k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
+k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
+k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
+k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M=
+k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY=
 k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs=
 k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU=
-k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A=
-k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks=
-k8s.io/code-generator v0.29.1 h1:8ba8BdtSmAVHgAMpzThb/fuyQeTRtN7NtN7VjMcDLew=
-k8s.io/code-generator v0.29.1/go.mod h1:FwFi3C9jCrmbPjekhaCYcYG1n07CYiW1+PAPCockaos=
-k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw=
-k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc=
-k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks=
-k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
-k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
-k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
-k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
-k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
+k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
+k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
+k8s.io/code-generator v0.30.0 h1:3VUVqHvWFSVSm9kqL/G6kD4ZwNdHF6J/jPyo3Jgjy3k=
+k8s.io/code-generator v0.30.0/go.mod h1:mBMZhfRR4IunJUh2+7LVmdcWwpouCH5+LNPkZ3t/v7Q=
+k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o=
+k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ=
+k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=
+k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
+k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
+k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
+k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
 k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s=
 k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4=
 k8s.io/metrics v0.29.1 h1:qutc3aIPMCniMuEApuLaeYX47rdCn8eycVDx7R6wMlQ=
 k8s.io/metrics v0.29.1/go.mod h1:JrbV2U71+v7d/9qb90UVKL8r0uJ6Z2Hy4V7mDm05cKs=
 k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
 k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2SGwkQasmbeqDo8th5wOBA5h/AjTKA4I=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/KrK4fjnV61bE2g3sA7tiETLn8sooImelsCx3Y=
-sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s=
-sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4=
+sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q=
+sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
 sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A=
 sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc=
 sigs.k8s.io/e2e-framework v0.3.0 h1:eqQALBtPCth8+ulTs6lcPK7ytV5rZSSHJzQHZph4O7U=
@@ -691,7 +685,6 @@ sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM
 sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=
 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
 sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/internal/controller/apiextensions/claim/reconciler.go b/internal/controller/apiextensions/claim/reconciler.go
index 061e74661..be4cc7d05 100644
--- a/internal/controller/apiextensions/claim/reconciler.go
+++ b/internal/controller/apiextensions/claim/reconciler.go
@@ -27,7 +27,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -37,7 +36,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 
@@ -221,14 +219,6 @@ func defaultCRClaim(c client.Client) crClaim {
 // A ReconcilerOption configures a Reconciler.
 type ReconcilerOption func(*Reconciler)
 
-// WithClient specifies how the Reconciler should interact with the Kubernetes
-// API.
-func WithClient(c client.Client) ReconcilerOption {
-	return func(r *Reconciler) {
-		r.client = c
-	}
-}
-
 // WithManagedFieldsUpgrader specifies how the Reconciler should upgrade claim
 // and composite resource (XR) managed fields from client-side apply to
 // server-side apply.
@@ -300,8 +290,7 @@ func WithPollInterval(after time.Duration) ReconcilerOption {
 // The returned Reconciler will apply only the ObjectMetaConfigurator by
 // default; most callers should supply one or more CompositeConfigurators to
 // configure their composite resources.
-func NewReconciler(m manager.Manager, of resource.CompositeClaimKind, with resource.CompositeKind, o ...ReconcilerOption) *Reconciler {
-	c := unstructured.NewClient(m.GetClient())
+func NewReconciler(c client.Client, of resource.CompositeClaimKind, with resource.CompositeKind, o ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
 		client:        c,
 		gvkClaim:      schema.GroupVersionKind(of),
diff --git a/internal/controller/apiextensions/composite/composition_functions.go b/internal/controller/apiextensions/composite/composition_functions.go
index a126dac18..d3b214c65 100644
--- a/internal/controller/apiextensions/composite/composition_functions.go
+++ b/internal/controller/apiextensions/composite/composition_functions.go
@@ -237,10 +237,6 @@ func WithManagedFieldsUpgrader(u ManagedFieldsUpgrader) FunctionComposerOption {
 // NewFunctionComposer returns a new Composer that supports composing resources using
 // both Patch and Transform (P&T) logic and a pipeline of Composition Functions.
 func NewFunctionComposer(kube client.Client, r FunctionRunner, o ...FunctionComposerOption) *FunctionComposer {
-	// TODO(negz): Can we avoid double-wrapping if the supplied client is
-	// already wrapped? Or just do away with unstructured.NewClient completely?
-	kube = unstructured.NewClient(kube)
-
 	f := NewSecretConnectionDetailsFetcher(kube)
 
 	c := &FunctionComposer{
@@ -651,7 +647,6 @@ func (e *ExistingExtraResourcesFetcher) Fetch(ctx context.Context, rs *v1beta1.R
 
 		resources := make([]*v1beta1.Resource, len(list.Items))
 		for i, r := range list.Items {
-			r := r
 			o, err := AsStruct(&r)
 			if err != nil {
 				return nil, errors.Wrap(err, errExtraResourceAsStruct)
diff --git a/internal/controller/apiextensions/composite/composition_pt.go b/internal/controller/apiextensions/composite/composition_pt.go
index 023afa2fa..39b710c07 100644
--- a/internal/controller/apiextensions/composite/composition_pt.go
+++ b/internal/controller/apiextensions/composite/composition_pt.go
@@ -32,7 +32,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 
@@ -126,10 +125,6 @@ type PTComposer struct {
 // NewPTComposer returns a Composer that composes resources using Patch and
 // Transform (P&T) Composition - a Composition's bases, patches, and transforms.
 func NewPTComposer(kube client.Client, o ...PTComposerOption) *PTComposer {
-	// TODO(negz): Can we avoid double-wrapping if the supplied client is
-	// already wrapped? Or just do away with unstructured.NewClient completely?
-	kube = unstructured.NewClient(kube)
-
 	c := &PTComposer{
 		client: resource.ClientApplicator{Client: kube, Applicator: resource.NewAPIPatchingApplicator(kube)},
 
@@ -416,7 +411,7 @@ func AssociateByOrder(t []v1.ComposedTemplate, r []corev1.ObjectReference) []Tem
 		j = len(r)
 	}
 
-	for i := 0; i < j; i++ {
+	for i := range j {
 		a[i].Reference = r[i]
 	}
 
diff --git a/internal/controller/apiextensions/composite/environment_selector.go b/internal/controller/apiextensions/composite/environment_selector.go
index 0eff504e6..f56bd8898 100644
--- a/internal/controller/apiextensions/composite/environment_selector.go
+++ b/internal/controller/apiextensions/composite/environment_selector.go
@@ -191,7 +191,7 @@ func sortConfigs(ec []v1alpha1.EnvironmentConfig, f string) error {
 	}, len(ec))
 
 	var valsKind reflect.Kind
-	for i := 0; i < len(ec); i++ {
+	for i := range len(ec) {
 		m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&ec[i])
 		if err != nil {
 			return err
diff --git a/internal/controller/apiextensions/composite/reconciler.go b/internal/controller/apiextensions/composite/reconciler.go
index 2d5b69304..47c13396c 100644
--- a/internal/controller/apiextensions/composite/reconciler.go
+++ b/internal/controller/apiextensions/composite/reconciler.go
@@ -26,13 +26,9 @@ import (
 
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/util/workqueue"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	runtimeevent "sigs.k8s.io/controller-runtime/pkg/event"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -42,10 +38,11 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+	"github.com/crossplane/crossplane/internal/engine"
 )
 
 const (
@@ -255,14 +252,6 @@ func WithPollInterval(interval time.Duration) ReconcilerOption {
 	})
 }
 
-// WithClient specifies how the Reconciler should interact with the Kubernetes
-// API.
-func WithClient(c client.Client) ReconcilerOption {
-	return func(r *Reconciler) {
-		r.client = c
-	}
-}
-
 // WithCompositionRevisionFetcher specifies how the composition to be used should be
 // fetched.
 func WithCompositionRevisionFetcher(f CompositionRevisionFetcher) ReconcilerOption {
@@ -336,11 +325,13 @@ func WithComposer(c Composer) ReconcilerOption {
 	}
 }
 
-// WithKindObserver specifies how the Reconciler should observe kinds for
-// realtime events.
-func WithKindObserver(o KindObserver) ReconcilerOption {
+// WithWatchStarter specifies how the Reconciler should start watches for any
+// resources it composes.
+func WithWatchStarter(controllerName string, h handler.EventHandler, w WatchStarter) ReconcilerOption {
 	return func(r *Reconciler) {
-		r.kindObserver = o
+		r.controllerName = controllerName
+		r.watchHandler = h
+		r.engine = w
 	}
 }
 
@@ -363,6 +354,27 @@ func (fn CompositionRevisionValidatorFn) Validate(c *v1.CompositionRevision) err
 	return fn(c)
 }
 
+// A WatchStarter can start a new watch. XR controllers use this to dynamically
+// start watches when they compose new kinds of resources.
+type WatchStarter interface {
+	// StartWatches starts the supplied watches, if they're not running already.
+	StartWatches(name string, ws ...engine.Watch) error
+}
+
+// A NopWatchStarter does nothing.
+type NopWatchStarter struct{}
+
+// StartWatches does nothing.
+func (n *NopWatchStarter) StartWatches(_ string, _ ...engine.Watch) error { return nil }
+
+// A WatchStarterFn is a function that can start a new watch.
+type WatchStarterFn func(name string, ws ...engine.Watch) error
+
+// StartWatches starts the supplied watches, if they're not running already.
+func (fn WatchStarterFn) StartWatches(name string, ws ...engine.Watch) error {
+	return fn(name, ws...)
+}
+
 type environment struct {
 	EnvironmentFetcher
 }
@@ -375,34 +387,15 @@ type compositeResource struct {
 	managed.ConnectionPublisher
 }
 
-// KindObserver tracks kinds of referenced composed resources in composite
-// resources in order to start watches for them for realtime events.
-type KindObserver interface {
-	// WatchComposedResources starts a watch of the given kinds to trigger reconciles when
-	// a referenced object of those kinds changes.
-	WatchComposedResources(kind ...schema.GroupVersionKind)
-}
-
-// KindObserverFunc implements KindObserver as a function.
-type KindObserverFunc func(kind ...schema.GroupVersionKind)
-
-// WatchComposedResources starts a watch of the given kinds to trigger reconciles when
-// a referenced object of those kinds changes.
-func (fn KindObserverFunc) WatchComposedResources(kind ...schema.GroupVersionKind) {
-	fn(kind...)
-}
-
 // NewReconciler returns a new Reconciler of composite resources.
-func NewReconciler(mgr manager.Manager, of resource.CompositeKind, opts ...ReconcilerOption) *Reconciler {
-	kube := unstructured.NewClient(mgr.GetClient())
-
+func NewReconciler(c client.Client, of resource.CompositeKind, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
-		client: kube,
+		client: c,
 
 		gvk: schema.GroupVersionKind(of),
 
 		revision: revision{
-			CompositionRevisionFetcher: NewAPIRevisionFetcher(resource.ClientApplicator{Client: kube, Applicator: resource.NewAPIPatchingApplicator(kube)}),
+			CompositionRevisionFetcher: NewAPIRevisionFetcher(resource.ClientApplicator{Client: c, Applicator: resource.NewAPIPatchingApplicator(c)}),
 			CompositionRevisionValidator: CompositionRevisionValidatorFn(func(rev *v1.CompositionRevision) error {
 				// TODO(negz): Presumably this validation will eventually be
 				// removed in favor of the new Composition validation
@@ -421,18 +414,21 @@ func NewReconciler(mgr manager.Manager, of resource.CompositeKind, opts ...Recon
 		},
 
 		composite: compositeResource{
-			Finalizer:           resource.NewAPIFinalizer(kube, finalizer),
-			CompositionSelector: NewAPILabelSelectorResolver(kube),
+			Finalizer:           resource.NewAPIFinalizer(c, finalizer),
+			CompositionSelector: NewAPILabelSelectorResolver(c),
 			EnvironmentSelector: NewNoopEnvironmentSelector(),
-			Configurator:        NewConfiguratorChain(NewAPINamingConfigurator(kube), NewAPIConfigurator(kube)),
+			Configurator:        NewConfiguratorChain(NewAPINamingConfigurator(c), NewAPIConfigurator(c)),
 
 			// TODO(negz): In practice this is a filtered publisher that will
 			// never filter any keys. Is there an unfiltered variant we could
 			// use by default instead?
-			ConnectionPublisher: NewAPIFilteredSecretPublisher(kube, []string{}),
+			ConnectionPublisher: NewAPIFilteredSecretPublisher(c, []string{}),
 		},
 
-		resource: NewPTComposer(kube),
+		resource: NewPTComposer(c),
+
+		// Dynamic watches are disabled by default.
+		engine: &NopWatchStarter{},
 
 		log:    logging.NewNopLogger(),
 		record: event.NewNopRecorder(),
@@ -458,8 +454,12 @@ type Reconciler struct {
 	revision  revision
 	composite compositeResource
 
-	resource     Composer
-	kindObserver KindObserver
+	resource Composer
+
+	// Used to dynamically start composed resource watches.
+	controllerName string
+	engine         WatchStarter
+	watchHandler   handler.EventHandler
 
 	log    logging.Logger
 	record event.Recorder
@@ -624,12 +624,17 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 
-	if r.kindObserver != nil {
-		var gvks []schema.GroupVersionKind
-		for _, ref := range xr.GetResourceReferences() {
-			gvks = append(gvks, ref.GroupVersionKind())
-		}
-		r.kindObserver.WatchComposedResources(gvks...)
+	ws := make([]engine.Watch, len(xr.GetResourceReferences()))
+	for i, ref := range xr.GetResourceReferences() {
+		ws[i] = engine.WatchFor(composed.New(composed.FromReference(ref)), engine.WatchTypeComposedResource, r.watchHandler)
+	}
+
+	// StartWatches is a no-op unless the realtime compositions feature flag is
+	// enabled. When the flag is enabled, the ControllerEngine that starts this
+	// controller also starts a garbage collector for its watches.
+	if err := r.engine.StartWatches(r.controllerName, ws...); err != nil {
+		// TODO(negz): If we stop polling this will be a more serious error.
+		log.Debug("Cannot start watches for composed resources. Relying on polling to know when they change.", "controller-name", r.controllerName, "error", err)
 	}
 
 	published, err := r.composite.PublishConnection(ctx, xr, res.ConnectionDetails)
@@ -731,51 +736,3 @@ func getComposerResourcesNames(cds []ComposedResource) []string {
 	}
 	return names
 }
-
-// EnqueueForCompositionRevisionFunc returns a function that enqueues (the
-// related) XRs when a new CompositionRevision is created. This speeds up
-// reconciliation of XRs on changes to the Composition by not having to wait for
-// the 60s sync period, but be instant.
-func EnqueueForCompositionRevisionFunc(of resource.CompositeKind, list func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error, log logging.Logger) func(ctx context.Context, createEvent runtimeevent.CreateEvent, q workqueue.RateLimitingInterface) {
-	return func(ctx context.Context, createEvent runtimeevent.CreateEvent, q workqueue.RateLimitingInterface) {
-		rev, ok := createEvent.Object.(*v1.CompositionRevision)
-		if !ok {
-			// should not happen
-			return
-		}
-
-		// get all XRs
-		xrs := kunstructured.UnstructuredList{}
-		xrs.SetGroupVersionKind(schema.GroupVersionKind(of))
-		xrs.SetKind(schema.GroupVersionKind(of).Kind + "List")
-		if err := list(ctx, &xrs); err != nil {
-			// logging is most we can do here. This is a programming error if it happens.
-			log.Info("cannot list in CompositionRevision handler", "type", schema.GroupVersionKind(of).String(), "error", err)
-			return
-		}
-
-		// enqueue all those that reference the Composition of this revision
-		compName := rev.Labels[v1.LabelCompositionName]
-		if compName == "" {
-			return
-		}
-		for _, u := range xrs.Items {
-			xr := composite.Unstructured{Unstructured: u}
-
-			// only automatic
-			if pol := xr.GetCompositionUpdatePolicy(); pol != nil && *pol == xpv1.UpdateManual {
-				continue
-			}
-
-			// only those that reference the right Composition
-			if ref := xr.GetCompositionReference(); ref == nil || ref.Name != compName {
-				continue
-			}
-
-			q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
-				Name:      xr.GetName(),
-				Namespace: xr.GetNamespace(),
-			}})
-		}
-	}
-}
diff --git a/internal/controller/apiextensions/composite/watch/watch.go b/internal/controller/apiextensions/composite/watch/watch.go
new file mode 100644
index 000000000..262e120b0
--- /dev/null
+++ b/internal/controller/apiextensions/composite/watch/watch.go
@@ -0,0 +1,153 @@
+/*
+Copyright 2024 The Crossplane 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 watch implements a garbage collector for composed resource watches.
+package watch
+
+import (
+	"context"
+	"time"
+
+	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
+
+	"github.com/crossplane/crossplane/internal/engine"
+)
+
+// A ControllerEngine can get and stop watches for a controller.
+type ControllerEngine interface {
+	GetWatches(name string) ([]engine.WatchID, error)
+	StopWatches(ctx context.Context, name string, ws ...engine.WatchID) (int, error)
+	GetClient() client.Client
+}
+
+// A GarbageCollector garbage collects watches for a single composite resource
+// (XR) controller. A watch is eligible for garbage collection when none of the
+// XRs the controller owns resource references its GVK. The garbage collector
+// periodically lists all of the controller's XRs to determine what GVKs they
+// still reference.
+type GarbageCollector struct {
+	controllerName string
+	xrGVK          schema.GroupVersionKind
+
+	engine ControllerEngine
+
+	log logging.Logger
+}
+
+// A GarbageCollectorOption configures a GarbageCollector.
+type GarbageCollectorOption func(gc *GarbageCollector)
+
+// WithLogger configures how a GarbageCollector should log.
+func WithLogger(l logging.Logger) GarbageCollectorOption {
+	return func(gc *GarbageCollector) {
+		gc.log = l
+	}
+}
+
+// NewGarbageCollector creates a new watch garbage collector for a controller.
+func NewGarbageCollector(name string, of resource.CompositeKind, ce ControllerEngine, o ...GarbageCollectorOption) *GarbageCollector {
+	gc := &GarbageCollector{
+		controllerName: name,
+		xrGVK:          schema.GroupVersionKind(of),
+		engine:         ce,
+		log:            logging.NewNopLogger(),
+	}
+	for _, fn := range o {
+		fn(gc)
+	}
+	return gc
+}
+
+// GarbageCollectWatches runs garbage collection at the specified interval,
+// until the supplied context is cancelled. It stops any watches for resource
+// types that are no longer composed by any composite resource (XR).
+func (gc *GarbageCollector) GarbageCollectWatches(ctx context.Context, interval time.Duration) {
+	t := time.NewTicker(interval)
+	defer t.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			gc.log.Debug("Stopping composite controller watch garbage collector", "error", ctx.Err())
+			return
+		case <-t.C:
+			if err := gc.GarbageCollectWatchesNow(ctx); err != nil {
+				gc.log.Info("Cannot garbage collect composite controller watches", "error", err)
+			}
+		}
+	}
+}
+
+// GarbageCollectWatchesNow stops any watches for resource types that are no
+// longer composed by any composite resource (XR). It's safe to call from
+// multiple goroutines.
+func (gc *GarbageCollector) GarbageCollectWatchesNow(ctx context.Context) error {
+	// List all XRs of the type we're interested in.
+	l := &kunstructured.UnstructuredList{}
+	l.SetAPIVersion(gc.xrGVK.GroupVersion().String())
+	l.SetKind(gc.xrGVK.Kind + "List")
+	if err := gc.engine.GetClient().List(ctx, l); err != nil {
+		return errors.Wrap(err, "cannot list composite resources")
+	}
+
+	// Build the set of GVKs they still reference.
+	used := make(map[engine.WatchID]bool)
+	for _, u := range l.Items {
+		xr := &composite.Unstructured{Unstructured: u}
+		for _, ref := range xr.GetResourceReferences() {
+			used[engine.WatchID{Type: engine.WatchTypeComposedResource, GVK: schema.FromAPIVersionAndKind(ref.APIVersion, ref.Kind)}] = true
+		}
+	}
+
+	// Get the set of running watches.
+	running, err := gc.engine.GetWatches(gc.controllerName)
+	if err != nil {
+		return errors.Wrap(err, "cannot get running watches")
+	}
+
+	stop := make([]engine.WatchID, 0)
+	for _, wid := range running {
+		if !used[wid] {
+			stop = append(stop, wid)
+		}
+	}
+
+	// No need to call StopWatches if there's nothing to stop.
+	if len(stop) == 0 {
+		return nil
+	}
+
+	// Stop any watches that are running, but not used.
+	//
+	// It's possible watches were started or stopped since we called GetWatches.
+	// That's fine. Stopping a watch that doesn't exist is a no-op, and if a
+	// watch was started that needs garbage collecting we'll get it eventually
+	// when GC runs again.
+	//
+	// It's also possible we'll stop a watch that's actually in use, if an XR
+	// started composing its GVK between when we built the map of used watches
+	// and here where we call StopWatches. It'll restart next time the XR
+	// controller calls StartWatches.
+	_, err = gc.engine.StopWatches(ctx, gc.controllerName, stop...)
+	return errors.Wrap(err, "cannot stop watches for composed resource types that are no longer referenced by any composite resource")
+}
diff --git a/internal/controller/apiextensions/composition/reconciler.go b/internal/controller/apiextensions/composition/reconciler.go
index f090dae5c..92d771421 100644
--- a/internal/controller/apiextensions/composition/reconciler.go
+++ b/internal/controller/apiextensions/composition/reconciler.go
@@ -36,7 +36,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
@@ -98,10 +97,8 @@ func WithRecorder(er event.Recorder) ReconcilerOption {
 
 // NewReconciler returns a Reconciler of Compositions.
 func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
-	kube := unstructured.NewClient(mgr.GetClient())
-
 	r := &Reconciler{
-		client: kube,
+		client: mgr.GetClient(),
 		log:    logging.NewNopLogger(),
 		record: event.NewNopRecorder(),
 	}
diff --git a/internal/controller/apiextensions/controller/options.go b/internal/controller/apiextensions/controller/options.go
index 57c42c723..f0fe3799b 100644
--- a/internal/controller/apiextensions/controller/options.go
+++ b/internal/controller/apiextensions/controller/options.go
@@ -20,6 +20,7 @@ package controller
 import (
 	"github.com/crossplane/crossplane-runtime/pkg/controller"
 
+	"github.com/crossplane/crossplane/internal/engine"
 	"github.com/crossplane/crossplane/internal/xfn"
 )
 
@@ -27,6 +28,9 @@ import (
 type Options struct {
 	controller.Options
 
+	// ControllerEngine used to dynamically start and stop controllers.
+	ControllerEngine *engine.ControllerEngine
+
 	// FunctionRunner used to run Composition Functions.
 	FunctionRunner *xfn.PackagedFunctionRunner
 }
diff --git a/internal/controller/apiextensions/definition/composed.go b/internal/controller/apiextensions/definition/composed.go
deleted file mode 100644
index 3abd6c1b0..000000000
--- a/internal/controller/apiextensions/definition/composed.go
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
-Copyright 2023 The Crossplane 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 definition
-
-import (
-	"context"
-	"strings"
-	"sync"
-
-	"github.com/google/uuid"
-	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	kcache "k8s.io/client-go/tools/cache"
-	"k8s.io/client-go/util/workqueue"
-	cache "sigs.k8s.io/controller-runtime/pkg/cache"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/cluster"
-	runtimeevent "sigs.k8s.io/controller-runtime/pkg/event"
-	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/predicate"
-	"sigs.k8s.io/controller-runtime/pkg/source"
-
-	"github.com/crossplane/crossplane-runtime/pkg/controller"
-	"github.com/crossplane/crossplane-runtime/pkg/logging"
-
-	"github.com/crossplane/crossplane/internal/xcrd"
-)
-
-// composedResourceInformers manages composed resource informers referenced by
-// composite resources. It serves as an event source for realtime notifications
-// of changed composed resources, with the composite reconcilers as sinks.
-// It keeps composed resource informers alive as long as there are composites
-// referencing them. In parallel, the composite reconcilers keep track of
-// references to composed resources, and inform composedResourceInformers about
-// them via the WatchComposedResources method.
-type composedResourceInformers struct {
-	log     logging.Logger
-	cluster cluster.Cluster
-
-	gvkRoutedCache *controller.GVKRoutedCache
-
-	lock sync.RWMutex // everything below is protected by this lock
-
-	// cdCaches holds the composed resource informers. These are dynamically
-	// started and stopped based on the composites that reference them.
-	cdCaches map[schema.GroupVersionKind]cdCache
-	// xrCaches holds the composite resource informers. We use them to find
-	// composites referencing a certain composed resource GVK. If no composite
-	// is left doing so, a composed resource informer is stopped.
-	xrCaches map[schema.GroupVersionKind]cache.Cache
-	sinks    map[string]func(ev runtimeevent.UpdateEvent) // by some uid
-}
-
-type cdCache struct {
-	cache    cache.Cache
-	cancelFn context.CancelFunc
-}
-
-var _ source.Source = &composedResourceInformers{}
-
-// Start implements source.Source, i.e. starting composedResourceInformers as
-// source with h as the sink of update events. It keeps sending events until
-// ctx is done.
-// Note that Start can be called multiple times to deliver events to multiple
-// (composite resource) controllers.
-func (i *composedResourceInformers) Start(ctx context.Context, h handler.EventHandler, q workqueue.RateLimitingInterface, ps ...predicate.Predicate) error {
-	id := uuid.New().String()
-
-	i.lock.Lock()
-	defer i.lock.Unlock()
-	i.sinks[id] = func(ev runtimeevent.UpdateEvent) {
-		for _, p := range ps {
-			if !p.Update(ev) {
-				return
-			}
-		}
-		h.Update(ctx, ev, q)
-	}
-
-	go func() {
-		<-ctx.Done()
-
-		i.lock.Lock()
-		defer i.lock.Unlock()
-		delete(i.sinks, id)
-	}()
-
-	return nil
-}
-
-// RegisterComposite registers a composite resource cache with its GVK.
-// Instances of this GVK will be considered to keep composed resource informers
-// alive.
-func (i *composedResourceInformers) RegisterComposite(gvk schema.GroupVersionKind, ca cache.Cache) {
-	i.lock.Lock()
-	defer i.lock.Unlock()
-
-	if i.xrCaches == nil {
-		i.xrCaches = make(map[schema.GroupVersionKind]cache.Cache)
-	}
-	i.xrCaches[gvk] = ca
-}
-
-// UnregisterComposite removes a composite resource cache from being considered
-// to keep composed resource informers alive.
-func (i *composedResourceInformers) UnregisterComposite(gvk schema.GroupVersionKind) {
-	i.lock.Lock()
-	defer i.lock.Unlock()
-	delete(i.xrCaches, gvk)
-}
-
-// WatchComposedResources starts informers for the given composed resource GVKs.
-// The is wired into the composite reconciler, which will call this method on
-// every reconcile to make composedResourceInformers aware of the composed
-// resources the given composite resource references.
-//
-// Note that this complements cleanupComposedResourceInformers which regularly
-// garbage collects composed resource informers that are no longer referenced by
-// any composite.
-func (i *composedResourceInformers) WatchComposedResources(gvks ...schema.GroupVersionKind) {
-	i.lock.RLock()
-	defer i.lock.RUnlock()
-
-	// start new informers
-	for _, gvk := range gvks {
-		if _, found := i.cdCaches[gvk]; found {
-			continue
-		}
-
-		log := i.log.WithValues("gvk", gvk.String())
-
-		ca, err := cache.New(i.cluster.GetConfig(), cache.Options{})
-		if err != nil {
-			log.Debug("failed creating a cache", "error", err)
-			continue
-		}
-
-		// don't forget to call cancelFn in error cases to avoid leaks. In the
-		// happy case it's called from the go routine starting the cache below.
-		ctx, cancelFn := context.WithCancel(context.Background())
-
-		u := kunstructured.Unstructured{}
-		u.SetGroupVersionKind(gvk)
-		inf, err := ca.GetInformer(ctx, &u, cache.BlockUntilSynced(false)) // don't block. We wait in the go routine below.
-		if err != nil {
-			cancelFn()
-			log.Debug("failed getting informer", "error", err)
-			continue
-		}
-
-		if _, err := inf.AddEventHandler(kcache.ResourceEventHandlerFuncs{
-			UpdateFunc: func(oldObj, newObj interface{}) {
-				old := oldObj.(client.Object) //nolint:forcetypeassert // Will always be client.Object.
-				obj := newObj.(client.Object) //nolint:forcetypeassert // Will always be client.Object.
-				if old.GetResourceVersion() == obj.GetResourceVersion() {
-					return
-				}
-
-				i.lock.RLock()
-				defer i.lock.RUnlock()
-
-				ev := runtimeevent.UpdateEvent{
-					ObjectOld: old,
-					ObjectNew: obj,
-				}
-				for _, handleFn := range i.sinks {
-					handleFn(ev)
-				}
-			},
-		}); err != nil {
-			cancelFn()
-			log.Debug("failed adding event handler", "error", err)
-			continue
-		}
-
-		go func() {
-			defer cancelFn()
-
-			log.Info("Starting composed resource watch")
-			_ = ca.Start(ctx)
-		}()
-
-		i.cdCaches[gvk] = cdCache{
-			cache:    ca,
-			cancelFn: cancelFn,
-		}
-
-		// wait for in the background, and only when synced add to the routed cache
-		go func(gvk schema.GroupVersionKind) {
-			if synced := ca.WaitForCacheSync(ctx); synced {
-				log.Debug("Composed resource cache synced")
-				i.gvkRoutedCache.AddDelegate(gvk, ca)
-			}
-		}(gvk)
-	}
-}
-
-// cleanupComposedResourceInformers garbage collects composed resource informers
-// that are no longer referenced by any composite resource.
-//
-// Note that this complements WatchComposedResources which starts informers for
-// the composed resources referenced by a composite resource.
-func (i *composedResourceInformers) cleanupComposedResourceInformers(ctx context.Context) {
-	crds := extv1.CustomResourceDefinitionList{}
-	if err := i.cluster.GetClient().List(ctx, &crds); err != nil {
-		i.log.Debug(errListCRDs, "error", err)
-		return
-	}
-
-	// copy map to avoid locking it for the entire duration of the loop
-	xrCaches := make(map[schema.GroupVersionKind]cache.Cache, len(i.xrCaches))
-	i.lock.RLock()
-	for gvk, ca := range i.xrCaches {
-		xrCaches[gvk] = ca
-	}
-	i.lock.RUnlock()
-
-	// find all CRDs that some XR is referencing. This is O(CRDs * XRDs * versions).
-	// In practice, CRDs are 1000ish max, and XRDs are 10ish. So this is
-	// fast enough for now. It's all in-memory.
-	referenced := make(map[schema.GroupVersionKind]bool)
-	for _, crd := range crds.Items {
-		crd := crd
-
-		if !xcrd.IsEstablished(crd.Status) {
-			continue
-		}
-
-		for _, v := range crd.Spec.Versions {
-			cdGVK := schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Spec.Names.Kind}
-			for xrGVK, xrCache := range xrCaches {
-				// list composites that reference this composed GVK
-				list := kunstructured.UnstructuredList{}
-				list.SetGroupVersionKind(xrGVK.GroupVersion().WithKind(xrGVK.Kind + "List"))
-				if err := xrCache.List(ctx, &list, client.MatchingFields{compositeResourceRefGVKsIndex: cdGVK.String()}); err != nil {
-					i.log.Debug("cannot list composite resources referencing a certain composed resource GVK", "error", err, "gvk", xrGVK.String(), "fieldSelector", compositeResourceRefGVKsIndex+"="+cdGVK.String())
-					continue
-				}
-				if len(list.Items) > 0 {
-					referenced[cdGVK] = true
-				}
-			}
-		}
-	}
-
-	// stop old informers
-	for gvk, inf := range i.cdCaches {
-		if referenced[gvk] {
-			continue
-		}
-		inf.cancelFn()
-		i.gvkRoutedCache.RemoveDelegate(gvk)
-		i.log.Info("Stopped composed resource watch", "gvk", gvk.String())
-		delete(i.cdCaches, gvk)
-	}
-}
-
-func parseAPIVersion(v string) (string, string) {
-	parts := strings.SplitN(v, "/", 2)
-	switch len(parts) {
-	case 1:
-		return "", parts[0]
-	case 2:
-		return parts[0], parts[1]
-	default:
-		return "", ""
-	}
-}
diff --git a/internal/controller/apiextensions/definition/handlers.go b/internal/controller/apiextensions/definition/handlers.go
new file mode 100644
index 000000000..d6f3fb1de
--- /dev/null
+++ b/internal/controller/apiextensions/definition/handlers.go
@@ -0,0 +1,115 @@
+/*
+Copyright 2024 The Crossplane 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 definition
+
+import (
+	"context"
+
+	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/util/workqueue"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	kevent "sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
+
+	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+)
+
+// EnqueueForCompositionRevision enqueues reconciles for all XRs that will use a
+// newly created CompositionRevision.
+func EnqueueForCompositionRevision(of resource.CompositeKind, c client.Reader, log logging.Logger) handler.Funcs {
+	return handler.Funcs{
+		CreateFunc: func(ctx context.Context, e kevent.CreateEvent, q workqueue.RateLimitingInterface) {
+			rev, ok := e.Object.(*v1.CompositionRevision)
+			if !ok {
+				// should not happen
+				return
+			}
+
+			// TODO(negz): Check whether the revision's compositeTypeRef matches
+			// the supplied CompositeKind. If it doesn't, we can return early.
+
+			// get all XRs
+			xrs := kunstructured.UnstructuredList{}
+			xrs.SetGroupVersionKind(schema.GroupVersionKind(of))
+			xrs.SetKind(schema.GroupVersionKind(of).Kind + "List")
+			// TODO(negz): Index XRs by composition revision name?
+			if err := c.List(ctx, &xrs); err != nil {
+				// logging is most we can do here. This is a programming error if it happens.
+				log.Info("cannot list in CompositionRevision handler", "type", schema.GroupVersionKind(of).String(), "error", err)
+				return
+			}
+
+			// enqueue all those that reference the Composition of this revision
+			compName := rev.Labels[v1.LabelCompositionName]
+			// TODO(negz): Check this before we get all XRs.
+			if compName == "" {
+				return
+			}
+			for _, u := range xrs.Items {
+				xr := composite.Unstructured{Unstructured: u}
+
+				// only automatic
+				if pol := xr.GetCompositionUpdatePolicy(); pol != nil && *pol == xpv1.UpdateManual {
+					continue
+				}
+
+				// only those that reference the right Composition
+				if ref := xr.GetCompositionReference(); ref == nil || ref.Name != compName {
+					continue
+				}
+
+				q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
+					Name:      xr.GetName(),
+					Namespace: xr.GetNamespace(),
+				}})
+			}
+		},
+	}
+}
+
+// EnqueueCompositeResources enqueues reconciles for all XRs that reference an
+// updated composed resource.
+func EnqueueCompositeResources(of resource.CompositeKind, c client.Reader, log logging.Logger) handler.Funcs {
+	return handler.Funcs{
+		UpdateFunc: func(ctx context.Context, ev kevent.UpdateEvent, q workqueue.RateLimitingInterface) {
+			xrGVK := schema.GroupVersionKind(of)
+			cdGVK := ev.ObjectNew.GetObjectKind().GroupVersionKind()
+			key := refKey(ev.ObjectNew.GetNamespace(), ev.ObjectNew.GetName(), cdGVK.Kind, cdGVK.GroupVersion().String())
+
+			composites := kunstructured.UnstructuredList{}
+			composites.SetGroupVersionKind(xrGVK.GroupVersion().WithKind(xrGVK.Kind + "List"))
+			if err := c.List(ctx, &composites, client.MatchingFields{compositeResourcesRefsIndex: key}); err != nil {
+				log.Debug("cannot list composite resources related to a composed resource change", "error", err, "gvk", xrGVK.String(), "fieldSelector", compositeResourcesRefsIndex+"="+key)
+				return
+			}
+
+			// queue those composites for reconciliation
+			for _, xr := range composites.Items {
+				log.Debug("Enqueueing composite resource because composed resource changed", "name", xr.GetName(), "cdGVK", cdGVK.String(), "cdName", ev.ObjectNew.GetName())
+				q.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: xr.GetName()}})
+			}
+		},
+	}
+}
diff --git a/internal/controller/apiextensions/definition/indexes.go b/internal/controller/apiextensions/definition/indexes.go
index 94074885f..bfb622e0f 100644
--- a/internal/controller/apiextensions/definition/indexes.go
+++ b/internal/controller/apiextensions/definition/indexes.go
@@ -17,54 +17,21 @@ limitations under the License.
 package definition
 
 import (
-	"context"
 	"fmt"
 
 	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/util/workqueue"
-	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	runtimeevent "sigs.k8s.io/controller-runtime/pkg/event"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
-	"github.com/crossplane/crossplane-runtime/pkg/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 )
 
 const (
-	// compositeResourceRefGVKsIndex is an index of all GroupKinds that
-	// are in use by a Composition. It indexes from spec.resourceRefs, not
-	// from spec.resources. Hence, it will also work with functions.
-	compositeResourceRefGVKsIndex = "compositeResourceRefsGVKs"
 	// compositeResourcesRefsIndex is an index of resourceRefs that are owned
 	// by a composite.
 	compositeResourcesRefsIndex = "compositeResourcesRefs"
 )
 
-var (
-	_ client.IndexerFunc = IndexCompositeResourceRefGVKs
-	_ client.IndexerFunc = IndexCompositeResourcesRefs
-)
-
-// IndexCompositeResourceRefGVKs assumes the passed object is a composite. It
-// returns gvk keys for every resource referenced in the composite.
-func IndexCompositeResourceRefGVKs(o client.Object) []string {
-	u, ok := o.(*kunstructured.Unstructured)
-	if !ok {
-		return nil // should never happen
-	}
-	xr := composite.Unstructured{Unstructured: *u}
-	refs := xr.GetResourceReferences()
-	keys := make([]string, 0, len(refs))
-	for _, ref := range refs {
-		group, version := parseAPIVersion(ref.APIVersion)
-		keys = append(keys, schema.GroupVersionKind{Group: group, Version: version, Kind: ref.Kind}.String())
-	}
-	// unification is done by the informer.
-	return keys
-}
+var _ client.IndexerFunc = IndexCompositeResourcesRefs
 
 // IndexCompositeResourcesRefs assumes the passed object is a composite. It
 // returns keys for every composed resource referenced in the composite.
@@ -85,23 +52,3 @@ func IndexCompositeResourcesRefs(o client.Object) []string {
 func refKey(ns, name, kind, apiVersion string) string {
 	return fmt.Sprintf("%s.%s.%s.%s", name, ns, kind, apiVersion)
 }
-
-func enqueueXRsForMR(ca cache.Cache, xrGVK schema.GroupVersionKind, log logging.Logger) func(ctx context.Context, ev runtimeevent.UpdateEvent, q workqueue.RateLimitingInterface) {
-	return func(ctx context.Context, ev runtimeevent.UpdateEvent, q workqueue.RateLimitingInterface) {
-		mrGVK := ev.ObjectNew.GetObjectKind().GroupVersionKind()
-		key := refKey(ev.ObjectNew.GetNamespace(), ev.ObjectNew.GetName(), mrGVK.Kind, mrGVK.GroupVersion().String())
-
-		composites := kunstructured.UnstructuredList{}
-		composites.SetGroupVersionKind(xrGVK.GroupVersion().WithKind(xrGVK.Kind + "List"))
-		if err := ca.List(ctx, &composites, client.MatchingFields{compositeResourcesRefsIndex: key}); err != nil {
-			log.Debug("cannot list composite resources related to a MR change", "error", err, "gvk", xrGVK.String(), "fieldSelector", compositeResourcesRefsIndex+"="+key)
-			return
-		}
-
-		// queue those composites for reconciliation
-		for _, xr := range composites.Items {
-			log.Info("Enqueueing composite resource because managed resource changed", "name", xr.GetName(), "mrGVK", mrGVK.String(), "mrName", ev.ObjectNew.GetName())
-			q.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: xr.GetName()}})
-		}
-	}
-}
diff --git a/internal/controller/apiextensions/definition/reconciler.go b/internal/controller/apiextensions/definition/reconciler.go
index 2fa6f0470..58ce14d66 100644
--- a/internal/controller/apiextensions/definition/reconciler.go
+++ b/internal/controller/apiextensions/definition/reconciler.go
@@ -28,20 +28,13 @@ import (
 	kmeta "k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/util/workqueue"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
-	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
-	runtimeevent "sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
-	"sigs.k8s.io/controller-runtime/pkg/source"
 
 	"github.com/crossplane/crossplane-runtime/pkg/connection"
 	"github.com/crossplane/crossplane-runtime/pkg/controller"
@@ -52,12 +45,13 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
 	"github.com/crossplane/crossplane/apis/secrets/v1alpha1"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/composite"
+	"github.com/crossplane/crossplane/internal/controller/apiextensions/composite/watch"
 	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
+	"github.com/crossplane/crossplane/internal/engine"
 	"github.com/crossplane/crossplane/internal/features"
 	"github.com/crossplane/crossplane/internal/xcrd"
 )
@@ -72,6 +66,8 @@ const (
 	errApplyCRD                       = "cannot apply rendered composite resource CustomResourceDefinition"
 	errUpdateStatus                   = "cannot update status of CompositeResourceDefinition"
 	errStartController                = "cannot start composite resource controller"
+	errStopController                 = "cannot stop composite resource controller"
+	errStartWatches                   = "cannot start composite resource controller watches"
 	errAddIndex                       = "cannot add composite GVK index"
 	errAddFinalizer                   = "cannot add composite resource finalizer"
 	errRemoveFinalizer                = "cannot remove composite resource finalizer"
@@ -96,12 +92,50 @@ const (
 )
 
 // A ControllerEngine can start and stop Kubernetes controllers on demand.
+//
+//nolint:interfacebloat // We use this interface to stub the engine for testing, and we need all of its functionality.
 type ControllerEngine interface {
+	Start(name string, o ...engine.ControllerOption) error
+	Stop(ctx context.Context, name string) error
 	IsRunning(name string) bool
-	Create(name string, o kcontroller.Options, w ...controller.Watch) (controller.NamedController, error)
-	Start(name string, o kcontroller.Options, w ...controller.Watch) error
-	Stop(name string)
-	Err(name string) error
+	GetWatches(name string) ([]engine.WatchID, error)
+	StartWatches(name string, ws ...engine.Watch) error
+	StopWatches(ctx context.Context, name string, ws ...engine.WatchID) (int, error)
+	GetClient() client.Client
+	GetFieldIndexer() client.FieldIndexer
+}
+
+// A NopEngine does nothing.
+type NopEngine struct{}
+
+// Start does nothing.
+func (e *NopEngine) Start(_ string, _ ...engine.ControllerOption) error { return nil }
+
+// Stop does nothing.
+func (e *NopEngine) Stop(_ context.Context, _ string) error { return nil }
+
+// IsRunning always returns true.
+func (e *NopEngine) IsRunning(_ string) bool { return true }
+
+// GetWatches does nothing.
+func (e *NopEngine) GetWatches(_ string) ([]engine.WatchID, error) { return nil, nil }
+
+// StartWatches does nothing.
+func (e *NopEngine) StartWatches(_ string, _ ...engine.Watch) error { return nil }
+
+// StopWatches does nothing.
+func (e *NopEngine) StopWatches(_ context.Context, _ string, _ ...engine.WatchID) (int, error) {
+	return 0, nil
+}
+
+// GetClient returns a nil client.
+func (e *NopEngine) GetClient() client.Client {
+	return nil
+}
+
+// GetFieldIndexer returns a nil field indexer.
+func (e *NopEngine) GetFieldIndexer() client.FieldIndexer {
+	return nil
 }
 
 // A CRDRenderer renders a CompositeResourceDefinition's corresponding
@@ -125,24 +159,12 @@ func (fn CRDRenderFn) Render(d *v1.CompositeResourceDefinition) (*extv1.CustomRe
 func Setup(mgr ctrl.Manager, o apiextensionscontroller.Options) error {
 	name := "defined/" + strings.ToLower(v1.CompositeResourceDefinitionGroupKind)
 
-	r := NewReconciler(mgr,
+	r := NewReconciler(NewClientApplicator(mgr.GetClient()),
 		WithLogger(o.Logger.WithValues("controller", name)),
 		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
+		WithControllerEngine(o.ControllerEngine),
 		WithOptions(o))
 
-	if o.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		// Register a runnable regularly checking whether the watch composed
-		// resources are still referenced by composite resources. If not, the
-		// composed resource informer is stopped.
-		if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
-			// Run every minute.
-			wait.UntilWithContext(ctx, r.xrInformers.cleanupComposedResourceInformers, time.Minute)
-			return nil
-		})); err != nil {
-			return errors.Wrap(err, errCannotAddInformerLoopToManager)
-		}
-	}
-
 	return ctrl.NewControllerManagedBy(mgr).
 		Named(name).
 		For(&v1.CompositeResourceDefinition{}).
@@ -158,7 +180,6 @@ type ReconcilerOption func(*Reconciler)
 func WithLogger(log logging.Logger) ReconcilerOption {
 	return func(r *Reconciler) {
 		r.log = log
-		r.xrInformers.log = log
 	}
 }
 
@@ -189,7 +210,7 @@ func WithFinalizer(f resource.Finalizer) ReconcilerOption {
 // lifecycles of composite controllers.
 func WithControllerEngine(c ControllerEngine) ReconcilerOption {
 	return func(r *Reconciler) {
-		r.composite.ControllerEngine = c
+		r.engine = c
 	}
 }
 
@@ -201,48 +222,29 @@ func WithCRDRenderer(c CRDRenderer) ReconcilerOption {
 	}
 }
 
-// WithClientApplicator specifies how the Reconciler should interact with the
-// Kubernetes API.
-func WithClientApplicator(ca resource.ClientApplicator) ReconcilerOption {
-	return func(r *Reconciler) {
-		r.client = ca
-	}
-}
-
 type definition struct {
 	CRDRenderer
-	ControllerEngine
 	resource.Finalizer
 }
 
-// NewReconciler returns a Reconciler of CompositeResourceDefinitions.
-func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
-	kube := unstructured.NewClient(mgr.GetClient())
-
-	ca := controller.NewGVKRoutedCache(mgr.GetScheme(), mgr.GetCache())
+// NewClientApplicator returns a ClientApplicator suitable for use by the
+// definition controller.
+func NewClientApplicator(c client.Client) resource.ClientApplicator {
+	// TODO(negz): Use server-side apply instead of a ClientApplicator.
+	return resource.ClientApplicator{Client: c, Applicator: resource.NewAPIUpdatingApplicator(c)}
+}
 
+// NewReconciler returns a Reconciler of CompositeResourceDefinitions.
+func NewReconciler(ca resource.ClientApplicator, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
-		mgr: mgr,
-
-		client: resource.ClientApplicator{
-			Client:     kube,
-			Applicator: resource.NewAPIUpdatingApplicator(kube),
-		},
+		client: ca,
 
 		composite: definition{
-			CRDRenderer:      CRDRenderFn(xcrd.ForCompositeResource),
-			ControllerEngine: controller.NewEngine(mgr),
-			Finalizer:        resource.NewAPIFinalizer(kube, finalizer),
+			CRDRenderer: CRDRenderFn(xcrd.ForCompositeResource),
+			Finalizer:   resource.NewAPIFinalizer(ca, finalizer),
 		},
 
-		xrInformers: composedResourceInformers{
-			log:     logging.NewNopLogger(),
-			cluster: mgr,
-
-			gvkRoutedCache: ca,
-			cdCaches:       make(map[schema.GroupVersionKind]cdCache),
-			sinks:          make(map[string]func(ev runtimeevent.UpdateEvent)),
-		},
+		engine: &NopEngine{},
 
 		log:    logging.NewNopLogger(),
 		record: event.NewNopRecorder(),
@@ -256,27 +258,24 @@ func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 		f(r)
 	}
 
-	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		// wrap the manager's cache to route requests to dynamically started
-		// informers for managed resources.
-		r.mgr = controller.WithGVKRoutedCache(ca, mgr)
-	}
-
 	return r
 }
 
 // A Reconciler reconciles CompositeResourceDefinitions.
 type Reconciler struct {
+	// This client should only be used by this XRD controller, not the XR
+	// controllers it manages. XR controllers should use the engine's client.
+	// This ensures XR controllers will use a client backed by the same cache
+	// used to power their watches.
 	client resource.ClientApplicator
-	mgr    manager.Manager
 
 	composite definition
 
+	engine ControllerEngine
+
 	log    logging.Logger
 	record event.Recorder
 
-	xrInformers composedResourceInformers
-
 	options apiextensionscontroller.Options
 }
 
@@ -337,11 +336,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// the (presumably exceedingly rare) latter case we'll orphan
 		// the CRD.
 		if !meta.WasCreated(crd) || !metav1.IsControlledBy(crd, d) {
-			// It's likely that we've already stopped this
-			// controller on a previous reconcile, but we try again
-			// just in case. This is a no-op if the controller was
-			// already stopped.
-			r.stopCompositeController(d)
+			// It's likely that we've already stopped this controller on a
+			// previous reconcile, but we try again just in case. This is a
+			// no-op if the controller was already stopped.
+			if err := r.engine.Stop(ctx, composite.ControllerName(d.GetName())); err != nil {
+				err = errors.Wrap(err, errStopController)
+				r.record.Event(d, event.Warning(reasonTerminateXR, err))
+				return reconcile.Result{}, err
+			}
 			log.Debug("Stopped composite resource controller")
 
 			if err := r.composite.RemoveFinalizer(ctx, d); err != nil {
@@ -391,9 +393,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			return reconcile.Result{Requeue: true}, nil
 		}
 
-		// The controller should be stopped before the deletion of CRD
-		// so that it doesn't crash.
-		r.stopCompositeController(d)
+		// The controller must be stopped before the deletion of the CRD so that
+		// it doesn't crash.
+		if err := r.engine.Stop(ctx, composite.ControllerName(d.GetName())); err != nil {
+			err = errors.Wrap(err, errStopController)
+			r.record.Event(d, event.Warning(reasonTerminateXR, err))
+			return reconcile.Result{}, err
+		}
 		log.Debug("Stopped composite resource controller")
 
 		if err := r.client.Delete(ctx, crd); resource.IgnoreNotFound(err) != nil {
@@ -441,36 +447,29 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		return reconcile.Result{Requeue: true}, nil
 	}
 
-	if err := r.composite.Err(composite.ControllerName(d.GetName())); err != nil {
-		log.Debug("Composite resource controller encountered an error", "error", err)
-	}
-
 	observed := d.Status.Controllers.CompositeResourceTypeRef
 	desired := v1.TypeReferenceTo(d.GetCompositeGroupVersionKind())
-	switch {
-	case observed.APIVersion != "" && observed != desired:
-		r.stopCompositeController(d)
+	if observed.APIVersion != "" && observed != desired {
+		if err := r.engine.Stop(ctx, composite.ControllerName(d.GetName())); err != nil {
+			err = errors.Wrap(err, errStopController)
+			r.record.Event(d, event.Warning(reasonEstablishXR, err))
+			return reconcile.Result{}, err
+		}
 		log.Debug("Referenceable version changed; stopped composite resource controller",
 			"observed-version", observed.APIVersion,
 			"desired-version", desired.APIVersion)
-	case r.composite.IsRunning(composite.ControllerName(d.GetName())):
+	}
+
+	if r.engine.IsRunning(composite.ControllerName(d.GetName())) {
 		log.Debug("Composite resource controller is running")
 		d.Status.SetConditions(v1.WatchingComposite())
 		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
-	default:
-		if err := r.composite.Err(composite.ControllerName(d.GetName())); err != nil {
-			log.Debug("Composite resource controller encountered an error. Going to restart it", "error", err)
-		} else {
-			log.Debug("Composite resource controller is not running. Going to start it")
-		}
 	}
 
-	ro := CompositeReconcilerOptions(r.options, d, r.client, r.log, r.record)
+	ro := r.CompositeReconcilerOptions(ctx, d)
 	ck := resource.CompositeKind(d.GetCompositeGroupVersionKind())
-	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		ro = append(ro, composite.WithKindObserver(composite.KindObserverFunc(r.xrInformers.WatchComposedResources)))
-	}
-	cr := composite.NewReconciler(r.mgr, ck, ro...)
+
+	cr := composite.NewReconciler(r.engine.GetClient(), ck, ro...)
 	ko := r.options.ForControllerRuntime()
 
 	// Most controllers use this type of rate limiter to backoff requeues from 1
@@ -483,101 +482,80 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	ko.Reconciler = ratelimiter.NewReconciler(composite.ControllerName(d.GetName()), errors.WithSilentRequeueOnConflict(cr), r.options.GlobalRateLimiter)
 
 	xrGVK := d.GetCompositeGroupVersionKind()
-
-	u := &kunstructured.Unstructured{}
-	u.SetGroupVersionKind(xrGVK)
-
 	name := composite.ControllerName(d.GetName())
-	var ca cache.Cache
-	watches := []controller.Watch{
-		controller.For(u, &handler.EnqueueRequestForObject{}),
-		// enqueue composites whenever a matching CompositionRevision is created
-		controller.TriggeredBy(source.Kind(r.mgr.GetCache(), &v1.CompositionRevision{}), handler.Funcs{
-			CreateFunc: composite.EnqueueForCompositionRevisionFunc(ck, r.mgr.GetCache().List, r.log),
-		}),
-	}
+
+	// TODO(negz): Update CompositeReconcilerOptions to produce
+	// ControllerOptions instead? It bothers me that this is the only feature
+	// flagged block outside that method.
+	co := []engine.ControllerOption{engine.WithRuntimeOptions(ko)}
 	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		// enqueue XRs that when a relevant MR is updated
-		watches = append(watches, controller.TriggeredBy(&r.xrInformers, handler.Funcs{
-			UpdateFunc: func(ctx context.Context, ev runtimeevent.UpdateEvent, q workqueue.RateLimitingInterface) {
-				enqueueXRsForMR(ca, xrGVK, log)(ctx, ev, q)
-			},
-		}))
+		// If realtime composition is enabled we'll start watches dynamically,
+		// so we want to garbage collect watches for composed resource kinds
+		// that aren't used anymore.
+		gc := watch.NewGarbageCollector(name, resource.CompositeKind(xrGVK), r.engine, watch.WithLogger(log))
+		co = append(co, engine.WithWatchGarbageCollector(gc))
 	}
 
-	c, err := r.composite.Create(name, ko, watches...)
-	if err != nil {
+	if err := r.engine.Start(name, co...); err != nil {
 		log.Debug(errStartController, "error", err)
 		err = errors.Wrap(err, errStartController)
 		r.record.Event(d, event.Warning(reasonEstablishXR, err))
 		return reconcile.Result{}, err
 	}
 
-	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		ca = c.GetCache()
-		if err := ca.IndexField(ctx, u, compositeResourceRefGVKsIndex, IndexCompositeResourceRefGVKs); err != nil {
-			log.Debug(errAddIndex, "error", err)
-			// Nothing we can do. At worst, we won't have realtime updates.
-		}
-		if err := ca.IndexField(ctx, u, compositeResourcesRefsIndex, IndexCompositeResourcesRefs); err != nil {
-			log.Debug(errAddIndex, "error", err)
-			// Nothing we can do. At worst, we won't have realtime updates.
-		}
-	}
+	// This must be *unstructured.Unstructured, not *composite.Unstructured.
+	// controller-runtime doesn't support watching types that satisfy the
+	// runtime.Unstructured interface - only *unstructured.Unstructured.
+	xr := &kunstructured.Unstructured{}
+	xr.SetGroupVersionKind(xrGVK)
 
-	if err := c.Start(context.Background()); err != nil { //nolint:contextcheck // the controller actually runs in the background.
-		log.Debug(errStartController, "error", err)
-		err = errors.Wrap(err, errStartController)
+	crh := EnqueueForCompositionRevision(resource.CompositeKind(xrGVK), r.engine.GetClient(), log)
+	if err := r.engine.StartWatches(name,
+		engine.WatchFor(xr, engine.WatchTypeCompositeResource, &handler.EnqueueRequestForObject{}),
+		engine.WatchFor(&v1.CompositionRevision{}, engine.WatchTypeCompositionRevision, crh),
+	); err != nil {
+		log.Debug(errStartWatches, "error", err)
+		err = errors.Wrap(err, errStartWatches)
 		r.record.Event(d, event.Warning(reasonEstablishXR, err))
 		return reconcile.Result{}, err
 	}
-	log.Debug("(Re)started composite resource controller")
 
-	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		r.xrInformers.RegisterComposite(xrGVK, ca)
-	}
+	log.Debug("Started composite resource controller")
 
 	d.Status.Controllers.CompositeResourceTypeRef = v1.TypeReferenceTo(d.GetCompositeGroupVersionKind())
 	d.Status.SetConditions(v1.WatchingComposite())
 	return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
 }
 
-func (r *Reconciler) stopCompositeController(d *v1.CompositeResourceDefinition) {
-	r.composite.Stop(composite.ControllerName(d.GetName()))
-	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
-		r.xrInformers.UnregisterComposite(d.GetCompositeGroupVersionKind())
-	}
-}
-
 // CompositeReconcilerOptions builds the options for a composite resource
 // reconciler. The options vary based on the supplied feature flags.
-func CompositeReconcilerOptions(co apiextensionscontroller.Options, d *v1.CompositeResourceDefinition, c client.Client, l logging.Logger, e event.Recorder) []composite.ReconcilerOption {
+func (r *Reconciler) CompositeReconcilerOptions(ctx context.Context, d *v1.CompositeResourceDefinition) []composite.ReconcilerOption {
 	// The default set of reconciler options when no feature flags are enabled.
 	o := []composite.ReconcilerOption{
-		composite.WithConnectionPublishers(composite.NewAPIFilteredSecretPublisher(c, d.GetConnectionSecretKeys())),
+		composite.WithConnectionPublishers(composite.NewAPIFilteredSecretPublisher(r.engine.GetClient(), d.GetConnectionSecretKeys())),
 		composite.WithCompositionSelector(composite.NewCompositionSelectorChain(
-			composite.NewEnforcedCompositionSelector(*d, e),
-			composite.NewAPIDefaultCompositionSelector(c, *meta.ReferenceTo(d, v1.CompositeResourceDefinitionGroupVersionKind), e),
-			composite.NewAPILabelSelectorResolver(c),
+			composite.NewEnforcedCompositionSelector(*d, r.record),
+			composite.NewAPIDefaultCompositionSelector(r.engine.GetClient(), *meta.ReferenceTo(d, v1.CompositeResourceDefinitionGroupVersionKind), r.record),
+			composite.NewAPILabelSelectorResolver(r.engine.GetClient()),
 		)),
-		composite.WithLogger(l.WithValues("controller", composite.ControllerName(d.GetName()))),
-		composite.WithRecorder(e.WithAnnotations("controller", composite.ControllerName(d.GetName()))),
-		composite.WithPollInterval(co.PollInterval),
+		composite.WithLogger(r.log.WithValues("controller", composite.ControllerName(d.GetName()))),
+		composite.WithRecorder(r.record.WithAnnotations("controller", composite.ControllerName(d.GetName()))),
+		composite.WithPollInterval(r.options.PollInterval),
 	}
 
 	// We only want to enable Composition environment support if the relevant
 	// feature flag is enabled. Otherwise we will default to noop selector and
 	// fetcher that will always return nil. All environment features are
 	// subsequently skipped if the environment is nil.
-	if co.Features.Enabled(features.EnableAlphaEnvironmentConfigs) {
+	if r.options.Features.Enabled(features.EnableAlphaEnvironmentConfigs) {
 		o = append(o,
-			composite.WithEnvironmentSelector(composite.NewAPIEnvironmentSelector(c)),
-			composite.WithEnvironmentFetcher(composite.NewAPIEnvironmentFetcher(c)))
+			composite.WithEnvironmentSelector(composite.NewAPIEnvironmentSelector(r.engine.GetClient())),
+			composite.WithEnvironmentFetcher(composite.NewAPIEnvironmentFetcher(r.engine.GetClient())))
 	}
 
 	// If external secret stores aren't enabled we just fetch connection details
 	// from Kubernetes secrets.
-	var fetcher managed.ConnectionDetailsFetcher = composite.NewSecretConnectionDetailsFetcher(c)
+	var fetcher managed.ConnectionDetailsFetcher = composite.NewSecretConnectionDetailsFetcher(r.engine.GetClient())
 
 	// We only want to enable ExternalSecretStore support if the relevant
 	// feature flag is enabled. Otherwise, we start the XR reconcilers with
@@ -585,48 +563,48 @@ func CompositeReconcilerOptions(co apiextensionscontroller.Options, d *v1.Compos
 	// We also add a new Configurator for ExternalSecretStore which basically
 	// reflects PublishConnectionDetailsWithStoreConfigRef in Composition to
 	// the composite resource.
-	if co.Features.Enabled(features.EnableAlphaExternalSecretStores) {
+	if r.options.Features.Enabled(features.EnableAlphaExternalSecretStores) {
 		pc := []managed.ConnectionPublisher{
-			composite.NewAPIFilteredSecretPublisher(c, d.GetConnectionSecretKeys()),
-			composite.NewSecretStoreConnectionPublisher(connection.NewDetailsManager(c, v1alpha1.StoreConfigGroupVersionKind,
-				connection.WithTLSConfig(co.ESSOptions.TLSConfig)), d.GetConnectionSecretKeys()),
+			composite.NewAPIFilteredSecretPublisher(r.engine.GetClient(), d.GetConnectionSecretKeys()),
+			composite.NewSecretStoreConnectionPublisher(connection.NewDetailsManager(r.engine.GetClient(), v1alpha1.StoreConfigGroupVersionKind,
+				connection.WithTLSConfig(r.options.ESSOptions.TLSConfig)), d.GetConnectionSecretKeys()),
 		}
 
 		// If external secret stores are enabled we need to support fetching
 		// connection details from both secrets and external stores.
 		fetcher = composite.ConnectionDetailsFetcherChain{
-			composite.NewSecretConnectionDetailsFetcher(c),
-			connection.NewDetailsManager(c, v1alpha1.StoreConfigGroupVersionKind, connection.WithTLSConfig(co.ESSOptions.TLSConfig)),
+			composite.NewSecretConnectionDetailsFetcher(r.engine.GetClient()),
+			connection.NewDetailsManager(r.engine.GetClient(), v1alpha1.StoreConfigGroupVersionKind, connection.WithTLSConfig(r.options.ESSOptions.TLSConfig)),
 		}
 
 		cc := composite.NewConfiguratorChain(
-			composite.NewAPINamingConfigurator(c),
-			composite.NewAPIConfigurator(c),
-			composite.NewSecretStoreConnectionDetailsConfigurator(c),
+			composite.NewAPINamingConfigurator(r.engine.GetClient()),
+			composite.NewAPIConfigurator(r.engine.GetClient()),
+			composite.NewSecretStoreConnectionDetailsConfigurator(r.engine.GetClient()),
 		)
 
 		o = append(o,
 			composite.WithConnectionPublishers(pc...),
 			composite.WithConfigurator(cc),
-			composite.WithComposer(composite.NewPTComposer(c, composite.WithComposedConnectionDetailsFetcher(fetcher))))
+			composite.WithComposer(composite.NewPTComposer(r.engine.GetClient(), composite.WithComposedConnectionDetailsFetcher(fetcher))))
 	}
 
 	// If Composition Functions are enabled we use two different Composer
 	// implementations. One supports P&T (aka 'Resources mode') and the other
 	// Functions (aka 'Pipeline mode').
-	if co.Features.Enabled(features.EnableBetaCompositionFunctions) {
-		ptc := composite.NewPTComposer(c, composite.WithComposedConnectionDetailsFetcher(fetcher))
+	if r.options.Features.Enabled(features.EnableBetaCompositionFunctions) {
+		ptc := composite.NewPTComposer(r.engine.GetClient(), composite.WithComposedConnectionDetailsFetcher(fetcher))
 
 		fcopts := []composite.FunctionComposerOption{
-			composite.WithComposedResourceObserver(composite.NewExistingComposedResourceObserver(c, fetcher)),
+			composite.WithComposedResourceObserver(composite.NewExistingComposedResourceObserver(r.engine.GetClient(), fetcher)),
 			composite.WithCompositeConnectionDetailsFetcher(fetcher),
 		}
 
-		if co.Features.Enabled(features.EnableBetaCompositionFunctionsExtraResources) {
-			fcopts = append(fcopts, composite.WithExtraResourcesFetcher(composite.NewExistingExtraResourcesFetcher(c)))
+		if r.options.Features.Enabled(features.EnableBetaCompositionFunctionsExtraResources) {
+			fcopts = append(fcopts, composite.WithExtraResourcesFetcher(composite.NewExistingExtraResourcesFetcher(r.engine.GetClient())))
 		}
 
-		fc := composite.NewFunctionComposer(c, co.FunctionRunner, fcopts...)
+		fc := composite.NewFunctionComposer(r.engine.GetClient(), r.options.FunctionRunner, fcopts...)
 
 		// Note that if external secret stores are enabled this will supersede
 		// the WithComposer option specified in that block.
@@ -649,5 +627,22 @@ func CompositeReconcilerOptions(co apiextensionscontroller.Options, d *v1.Compos
 		})))
 	}
 
+	// If realtime compositions are enabled we pass the ControllerEngine to the
+	// XR reconciler so that it can start watches for composed resources.
+	if r.options.Features.Enabled(features.EnableAlphaRealtimeCompositions) {
+		gvk := d.GetCompositeGroupVersionKind()
+		u := &kunstructured.Unstructured{}
+		u.SetAPIVersion(gvk.GroupVersion().String())
+		u.SetKind(gvk.Kind)
+
+		// Add an index to the controller engine's client.
+		if err := r.engine.GetFieldIndexer().IndexField(ctx, u, compositeResourcesRefsIndex, IndexCompositeResourcesRefs); err != nil {
+			r.log.Debug(errAddIndex, "error", err)
+		}
+
+		h := EnqueueCompositeResources(resource.CompositeKind(d.GetCompositeGroupVersionKind()), r.engine.GetClient(), r.log)
+		o = append(o, composite.WithWatchStarter(composite.ControllerName(d.GetName()), h, r.engine))
+	}
+
 	return o
 }
diff --git a/internal/controller/apiextensions/offered/reconciler.go b/internal/controller/apiextensions/offered/reconciler.go
index 2111294d9..86dc59fe7 100644
--- a/internal/controller/apiextensions/offered/reconciler.go
+++ b/internal/controller/apiextensions/offered/reconciler.go
@@ -31,9 +31,8 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
-	kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	"github.com/crossplane/crossplane-runtime/pkg/connection"
@@ -44,12 +43,12 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
 	secretsv1alpha1 "github.com/crossplane/crossplane/apis/secrets/v1alpha1"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/claim"
 	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
+	"github.com/crossplane/crossplane/internal/engine"
 	"github.com/crossplane/crossplane/internal/features"
 	"github.com/crossplane/crossplane/internal/names"
 	"github.com/crossplane/crossplane/internal/xcrd"
@@ -68,6 +67,8 @@ const (
 	errApplyCRD        = "cannot apply rendered composite resource claim CustomResourceDefinition"
 	errUpdateStatus    = "cannot update status of CompositeResourceDefinition"
 	errStartController = "cannot start composite resource claim controller"
+	errStopController  = "cannot stop composite resource claim controller"
+	errStartWatches    = "cannot start composite resource claim controller watches"
 	errAddFinalizer    = "cannot add composite resource claim finalizer"
 	errRemoveFinalizer = "cannot remove composite resource claim finalizer"
 	errDeleteCRD       = "cannot delete composite resource claim CustomResourceDefinition"
@@ -90,10 +91,36 @@ const (
 
 // A ControllerEngine can start and stop Kubernetes controllers on demand.
 type ControllerEngine interface {
+	Start(name string, o ...engine.ControllerOption) error
+	Stop(ctx context.Context, name string) error
 	IsRunning(name string) bool
-	Start(name string, o kcontroller.Options, w ...controller.Watch) error
-	Stop(name string)
-	Err(name string) error
+	StartWatches(name string, ws ...engine.Watch) error
+	GetClient() client.Client
+}
+
+// A NopEngine does nothing.
+type NopEngine struct{}
+
+// Start does nothing.
+func (e *NopEngine) Start(_ string, _ ...engine.ControllerOption) error { return nil }
+
+// Stop does nothing.
+func (e *NopEngine) Stop(_ context.Context, _ string) error { return nil }
+
+// IsRunning always returns true.
+func (e *NopEngine) IsRunning(_ string) bool { return true }
+
+// StartWatches does nothing.
+func (e *NopEngine) StartWatches(_ string, _ ...engine.Watch) error { return nil }
+
+// GetClient returns a nil client.
+func (e *NopEngine) GetClient() client.Client {
+	return nil
+}
+
+// GetFieldIndexer returns a nil field indexer.
+func (e *NopEngine) GetFieldIndexer() client.FieldIndexer {
+	return nil
 }
 
 // A CRDRenderer renders a CompositeResourceDefinition's corresponding
@@ -118,9 +145,10 @@ func (fn CRDRenderFn) Render(d *v1.CompositeResourceDefinition) (*extv1.CustomRe
 func Setup(mgr ctrl.Manager, o apiextensionscontroller.Options) error {
 	name := "offered/" + strings.ToLower(v1.CompositeResourceDefinitionGroupKind)
 
-	r := NewReconciler(mgr,
+	r := NewReconciler(NewClientApplicator(mgr.GetClient()),
 		WithLogger(o.Logger.WithValues("controller", name)),
 		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
+		WithControllerEngine(o.ControllerEngine),
 		WithOptions(o))
 
 	return ctrl.NewControllerManagedBy(mgr).
@@ -168,7 +196,7 @@ func WithFinalizer(f resource.Finalizer) ReconcilerOption {
 // lifecycles of claim controllers.
 func WithControllerEngine(c ControllerEngine) ReconcilerOption {
 	return func(r *Reconciler) {
-		r.claim.ControllerEngine = c
+		r.engine = c
 	}
 }
 
@@ -180,32 +208,25 @@ func WithCRDRenderer(c CRDRenderer) ReconcilerOption {
 	}
 }
 
-// WithClientApplicator specifies how the Reconciler should interact with the
-// Kubernetes API.
-func WithClientApplicator(ca resource.ClientApplicator) ReconcilerOption {
-	return func(r *Reconciler) {
-		r.client = ca
-	}
+// NewClientApplicator returns a ClientApplicator suitable for use by the
+// offered controller.
+func NewClientApplicator(c client.Client) resource.ClientApplicator {
+	// TODO(negz): Use server-side apply instead of a ClientApplicator.
+	return resource.ClientApplicator{Client: c, Applicator: resource.NewAPIUpdatingApplicator(c)}
 }
 
 // NewReconciler returns a Reconciler of CompositeResourceDefinitions.
-func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
-	kube := unstructured.NewClient(mgr.GetClient())
-
+func NewReconciler(ca resource.ClientApplicator, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
-		mgr: mgr,
-
-		client: resource.ClientApplicator{
-			Client:     kube,
-			Applicator: resource.NewAPIUpdatingApplicator(kube),
-		},
+		client: ca,
 
 		claim: definition{
-			CRDRenderer:      CRDRenderFn(xcrd.ForCompositeResourceClaim),
-			ControllerEngine: controller.NewEngine(mgr),
-			Finalizer:        resource.NewAPIFinalizer(kube, finalizer),
+			CRDRenderer: CRDRenderFn(xcrd.ForCompositeResourceClaim),
+			Finalizer:   resource.NewAPIFinalizer(ca, finalizer),
 		},
 
+		engine: &NopEngine{},
+
 		log:    logging.NewNopLogger(),
 		record: event.NewNopRecorder(),
 
@@ -222,17 +243,21 @@ func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 
 type definition struct {
 	CRDRenderer
-	ControllerEngine
 	resource.Finalizer
 }
 
 // A Reconciler reconciles CompositeResourceDefinitions.
 type Reconciler struct {
-	mgr    manager.Manager
+	// This client should only be used by this XRD controller, not the claim
+	// controllers it manages. Claim controllers should use the engine's client.
+	// This ensures claim controllers will use a client backed by the same cache
+	// used to power their watches.
 	client resource.ClientApplicator
 
 	claim definition
 
+	engine ControllerEngine
+
 	log    logging.Logger
 	record event.Recorder
 
@@ -293,11 +318,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// the (presumably exceedingly rare) latter case we'll orphan
 		// the CRD.
 		if !meta.WasCreated(crd) || !metav1.IsControlledBy(crd, d) {
-			// It's likely that we've already stopped this
-			// controller on a previous reconcile, but we try again
-			// just in case. This is a no-op if the controller was
-			// already stopped.
-			r.claim.Stop(claim.ControllerName(d.GetName()))
+			// It's likely that we've already stopped this controller on a
+			// previous reconcile, but we try again just in case. This is a
+			// no-op if the controller was already stopped.
+			if err := r.engine.Stop(ctx, claim.ControllerName(d.GetName())); err != nil {
+				err = errors.Wrap(err, errStopController)
+				r.record.Event(d, event.Warning(reasonRedactXRC, err))
+				return reconcile.Result{}, err
+			}
 			log.Debug("Stopped composite resource claim controller")
 
 			if err := r.claim.RemoveFinalizer(ctx, d); err != nil {
@@ -348,9 +376,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			return reconcile.Result{Requeue: true}, nil
 		}
 
-		// The controller should be stopped before the deletion of CRD
-		// so that it doesn't crash.
-		r.claim.Stop(claim.ControllerName(d.GetName()))
+		// The controller should be stopped before the deletion of CRD so that
+		// it doesn't crash.
+		if err := r.engine.Stop(ctx, claim.ControllerName(d.GetName())); err != nil {
+			err = errors.Wrap(err, errStopController)
+			r.record.Event(d, event.Warning(reasonRedactXRC, err))
+			return reconcile.Result{}, err
+		}
 		log.Debug("Stopped composite resource claim controller")
 
 		if err := r.client.Delete(ctx, crd); resource.IgnoreNotFound(err) != nil {
@@ -407,8 +439,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// upgrading fields that were previously managed using client-side apply.
 	if r.options.Features.Enabled(features.EnableAlphaClaimSSA) {
 		o = append(o,
-			claim.WithCompositeSyncer(claim.NewServerSideCompositeSyncer(r.client, names.NewNameGenerator(r.client))),
-			claim.WithManagedFieldsUpgrader(claim.NewPatchingManagedFieldsUpgrader(r.client)),
+			claim.WithCompositeSyncer(claim.NewServerSideCompositeSyncer(r.engine.GetClient(), names.NewNameGenerator(r.engine.GetClient()))),
+			claim.WithManagedFieldsUpgrader(claim.NewPatchingManagedFieldsUpgrader(r.engine.GetClient())),
 		)
 	}
 
@@ -417,50 +449,64 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// their default Connection Propagator.
 	if r.options.Features.Enabled(features.EnableAlphaExternalSecretStores) {
 		pc := claim.ConnectionPropagatorChain{
-			claim.NewAPIConnectionPropagator(r.client),
-			connection.NewDetailsManager(r.client, secretsv1alpha1.StoreConfigGroupVersionKind, connection.WithTLSConfig(r.options.ESSOptions.TLSConfig)),
+			claim.NewAPIConnectionPropagator(r.engine.GetClient()),
+			connection.NewDetailsManager(r.engine.GetClient(), secretsv1alpha1.StoreConfigGroupVersionKind, connection.WithTLSConfig(r.options.ESSOptions.TLSConfig)),
 		}
 
 		o = append(o, claim.WithConnectionPropagator(pc), claim.WithConnectionUnpublisher(
-			claim.NewSecretStoreConnectionUnpublisher(connection.NewDetailsManager(r.client,
+			claim.NewSecretStoreConnectionUnpublisher(connection.NewDetailsManager(r.engine.GetClient(),
 				secretsv1alpha1.StoreConfigGroupVersionKind, connection.WithTLSConfig(r.options.ESSOptions.TLSConfig)))))
 	}
 
-	cr := claim.NewReconciler(r.mgr,
-		resource.CompositeClaimKind(d.GetClaimGroupVersionKind()),
-		resource.CompositeKind(d.GetCompositeGroupVersionKind()), o...)
-
-	ko := r.options.ForControllerRuntime()
-	ko.Reconciler = ratelimiter.NewReconciler(claim.ControllerName(d.GetName()), errors.WithSilentRequeueOnConflict(cr), r.options.GlobalRateLimiter)
-
-	if err := r.claim.Err(claim.ControllerName(d.GetName())); err != nil {
-		log.Debug("Composite resource controller encountered an error", "error", err)
-	}
-
 	observed := d.Status.Controllers.CompositeResourceClaimTypeRef
 	desired := v1.TypeReferenceTo(d.GetClaimGroupVersionKind())
 	if observed.APIVersion != "" && observed != desired {
-		r.claim.Stop(claim.ControllerName(d.GetName()))
+		if err := r.engine.Stop(ctx, claim.ControllerName(d.GetName())); err != nil {
+			err = errors.Wrap(err, errStopController)
+			r.record.Event(d, event.Warning(reasonOfferXRC, err))
+			return reconcile.Result{}, err
+		}
 		log.Debug("Referenceable version changed; stopped composite resource claim controller",
 			"observed-version", observed.APIVersion,
 			"desired-version", desired.APIVersion)
 	}
 
+	if r.engine.IsRunning(claim.ControllerName(d.GetName())) {
+		log.Debug("Composite resource claim controller is running")
+		d.Status.SetConditions(v1.WatchingClaim())
+		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
+	}
+
+	cr := claim.NewReconciler(r.engine.GetClient(),
+		resource.CompositeClaimKind(d.GetClaimGroupVersionKind()),
+		resource.CompositeKind(d.GetCompositeGroupVersionKind()), o...)
+
+	ko := r.options.ForControllerRuntime()
+	ko.Reconciler = ratelimiter.NewReconciler(claim.ControllerName(d.GetName()), errors.WithSilentRequeueOnConflict(cr), r.options.GlobalRateLimiter)
+
+	if err := r.engine.Start(claim.ControllerName(d.GetName()), engine.WithRuntimeOptions(ko)); err != nil {
+		err = errors.Wrap(err, errStartController)
+		r.record.Event(d, event.Warning(reasonOfferXRC, err))
+		return reconcile.Result{}, err
+	}
+	log.Debug("Started composite resource claim controller")
+
+	// These must be *unstructured.Unstructured, not e.g. *claim.Unstructured.
+	// controller-runtime doesn't support watching types that satisfy the
+	// runtime.Unstructured interface - only *unstructured.Unstructured.
 	cm := &kunstructured.Unstructured{}
 	cm.SetGroupVersionKind(d.GetClaimGroupVersionKind())
+	xr := &kunstructured.Unstructured{}
+	xr.SetGroupVersionKind(d.GetCompositeGroupVersionKind())
 
-	cp := &kunstructured.Unstructured{}
-	cp.SetGroupVersionKind(d.GetCompositeGroupVersionKind())
-
-	if err := r.claim.Start(claim.ControllerName(d.GetName()), ko,
-		controller.For(cm, &handler.EnqueueRequestForObject{}),
-		controller.For(cp, &EnqueueRequestForClaim{}),
+	if err := r.engine.StartWatches(claim.ControllerName(d.GetName()),
+		engine.WatchFor(cm, engine.WatchTypeClaim, &handler.EnqueueRequestForObject{}),
+		engine.WatchFor(xr, engine.WatchTypeCompositeResource, &EnqueueRequestForClaim{}),
 	); err != nil {
-		err = errors.Wrap(err, errStartController)
+		err = errors.Wrap(err, errStartWatches)
 		r.record.Event(d, event.Warning(reasonOfferXRC, err))
 		return reconcile.Result{}, err
 	}
-	log.Debug("(Re)started composite resource claim controller")
 
 	d.Status.Controllers.CompositeResourceClaimTypeRef = v1.TypeReferenceTo(d.GetClaimGroupVersionKind())
 	d.Status.SetConditions(v1.WatchingClaim())
diff --git a/internal/controller/apiextensions/usage/reconciler.go b/internal/controller/apiextensions/usage/reconciler.go
index 8ed3fc670..58f50a2a5 100644
--- a/internal/controller/apiextensions/usage/reconciler.go
+++ b/internal/controller/apiextensions/usage/reconciler.go
@@ -171,6 +171,12 @@ type usageResource struct {
 
 // NewReconciler returns a Reconciler of Usages.
 func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
+	// TODO(negz): Stop using this wrapper? It's only necessary if the client is
+	// backed by a cache, and at the time of writing the manager's client isn't.
+	// It's configured not to automatically cache unstructured objects. The
+	// wrapper is needed when caching because controller-runtime doesn't support
+	// caching types that satisfy runtime.Unstructured - it only supports the
+	// concrete *unstructured.Unstructured type.
 	kube := unstructured.NewClient(mgr.GetClient())
 
 	r := &Reconciler{
@@ -320,14 +326,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		if u.Spec.ReplayDeletion != nil && *u.Spec.ReplayDeletion && used.GetAnnotations() != nil {
 			if policy, ok := used.GetAnnotations()[usage.AnnotationKeyDeletionAttempt]; ok {
 				// We have already recorded a deletion attempt and want to replay deletion, let's delete the used resource.
-				//nolint:contextcheck // See comment on Delete below.
+
+				//nolint:contextcheck // We cannot use the context from the reconcile function since it will be cancelled after the reconciliation.
 				go func() {
 					// We do the deletion async and after some delay to make sure the usage is deleted before the
 					// deletion attempt. We remove the finalizer on this Usage right below, so, we know it will disappear
 					// very soon.
 					time.Sleep(2 * time.Second)
 					log.Info("Replaying deletion of the used resource", "apiVersion", used.GetAPIVersion(), "kind", used.GetKind(), "name", used.GetName(), "policy", policy)
-					// We cannot use the context from the reconcile function since it will be cancelled after the reconciliation.
 					if err = r.client.Delete(context.Background(), used, client.PropagationPolicy(policy)); err != nil {
 						log.Info("Error when replaying deletion of the used resource", "apiVersion", used.GetAPIVersion(), "kind", used.GetKind(), "name", used.GetName(), "err", err)
 					}
diff --git a/internal/controller/pkg/revision/establisher.go b/internal/controller/pkg/revision/establisher.go
index cac651785..bd5f1471f 100644
--- a/internal/controller/pkg/revision/establisher.go
+++ b/internal/controller/pkg/revision/establisher.go
@@ -145,7 +145,6 @@ func (e *APIEstablisher) ReleaseObjects(ctx context.Context, parent v1.PackageRe
 	g, ctx := errgroup.WithContext(ctx)
 	g.SetLimit(maxConcurrentEstablishers)
 	for _, ref := range allObjs {
-		ref := ref // Pin the loop variable.
 		g.Go(func() error {
 			select {
 			case <-ctx.Done():
@@ -235,7 +234,6 @@ func (e *APIEstablisher) validate(ctx context.Context, objs []runtime.Object, pa
 	g.SetLimit(maxConcurrentEstablishers)
 	out := make(chan currentDesired, len(objs))
 	for _, res := range objs {
-		res := res // Pin the range variable before using it in a Goroutine.
 		g.Go(func() error {
 			// Assert desired object to resource.Object so that we can access its
 			// metadata.
@@ -393,7 +391,6 @@ func (e *APIEstablisher) establish(ctx context.Context, allObjs []currentDesired
 	g.SetLimit(maxConcurrentEstablishers)
 	out := make(chan xpv1.TypedReference, len(allObjs))
 	for _, cd := range allObjs {
-		cd := cd // Pin the loop variable.
 		g.Go(func() error {
 			if !cd.Exists {
 				// Only create a missing resource if we are going to control it.
diff --git a/internal/controller/rbac/definition/reconciler.go b/internal/controller/rbac/definition/reconciler.go
index 6667bc0a6..bc4263aef 100644
--- a/internal/controller/rbac/definition/reconciler.go
+++ b/internal/controller/rbac/definition/reconciler.go
@@ -181,7 +181,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	applied := make([]string, 0)
 	for _, cr := range r.rbac.RenderClusterRoles(d) {
-		cr := cr // Pin range variable so we can take its address.
 		log := log.WithValues("role-name", cr.GetName())
 		origRV := ""
 		err := r.client.Apply(ctx, &cr,
diff --git a/internal/controller/rbac/provider/roles/reconciler.go b/internal/controller/rbac/provider/roles/reconciler.go
index 2970620fd..df35d3e91 100644
--- a/internal/controller/rbac/provider/roles/reconciler.go
+++ b/internal/controller/rbac/provider/roles/reconciler.go
@@ -333,7 +333,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	applied := make([]string, 0)
 	for _, cr := range r.rbac.RenderClusterRoles(pr, resources) {
-		cr := cr // Pin range variable so we can take its address.
 		log := log.WithValues("role-name", cr.GetName())
 		origRV := ""
 		err := r.client.Apply(ctx, &cr,
diff --git a/internal/engine/cache.go b/internal/engine/cache.go
new file mode 100644
index 000000000..c1ccca089
--- /dev/null
+++ b/internal/engine/cache.go
@@ -0,0 +1,189 @@
+/*
+Copyright 2024 The Crossplane 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 engine
+
+import (
+	"context"
+	"strings"
+	"sync"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+)
+
+var (
+	_ cache.Cache       = &InformerTrackingCache{}
+	_ TrackingInformers = &InformerTrackingCache{}
+)
+
+// An InformerTrackingCache wraps a cache.Cache and keeps track of what GVKs it
+// has started informers for. It takes a blocking lock whenever a new informer
+// is started or stopped, but so does the standard controller-runtime Cache
+// implementation.
+type InformerTrackingCache struct {
+	// The wrapped cache.
+	cache.Cache
+
+	scheme *runtime.Scheme
+
+	mx     sync.RWMutex
+	active map[schema.GroupVersionKind]bool
+}
+
+// TrackInformers wraps the supplied cache, adding a method to query which
+// informers are active.
+func TrackInformers(c cache.Cache, s *runtime.Scheme) *InformerTrackingCache {
+	return &InformerTrackingCache{
+		Cache:  c,
+		scheme: s,
+		active: make(map[schema.GroupVersionKind]bool),
+	}
+}
+
+// ActiveInformers returns the GVKs of the informers believed to currently be
+// active. The InformerTrackingCache considers an informer to become active when
+// a caller calls Get, List, or one of the GetInformer methods. It considers an
+// informer to become inactive when a caller calls the RemoveInformer method.
+func (c *InformerTrackingCache) ActiveInformers() []schema.GroupVersionKind {
+	c.mx.RLock()
+	defer c.mx.RUnlock()
+
+	out := make([]schema.GroupVersionKind, 0, len(c.active))
+	for gvk := range c.active {
+		out = append(out, gvk)
+	}
+	return out
+}
+
+// Get retrieves an obj for the given object key from the Kubernetes Cluster.
+// obj must be a struct pointer so that obj can be updated with the response
+// returned by the Server.
+//
+// Getting an object marks the informer for the object's GVK active.
+func (c *InformerTrackingCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return errors.Wrap(err, "cannot determine group, version, and kind of supplied object")
+	}
+
+	c.mx.RLock()
+	if _, active := c.active[gvk]; active {
+		defer c.mx.RUnlock()
+		return c.Cache.Get(ctx, key, obj, opts...)
+	}
+	c.mx.RUnlock()
+
+	c.mx.Lock()
+	defer c.mx.Unlock()
+	c.active[gvk] = true
+	return c.Cache.Get(ctx, key, obj, opts...)
+}
+
+// List retrieves list of objects for a given namespace and list options. On a
+// successful call, Items field in the list will be populated with the result
+// returned from the server.
+//
+// Listing objects marks the informer for the object's GVK active.
+func (c *InformerTrackingCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
+	gvk, err := apiutil.GVKForObject(list, c.scheme)
+	if err != nil {
+		return errors.Wrap(err, "cannot determine group, version, and kind of supplied object")
+	}
+	gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
+
+	c.mx.RLock()
+	if _, active := c.active[gvk]; active {
+		defer c.mx.RUnlock()
+		return c.Cache.List(ctx, list, opts...)
+	}
+	c.mx.RUnlock()
+
+	c.mx.Lock()
+	defer c.mx.Unlock()
+	c.active[gvk] = true
+	return c.Cache.List(ctx, list, opts...)
+}
+
+// GetInformer fetches or constructs an informer for the given object that
+// corresponds to a single API kind and resource.
+//
+// Getting an informer for an object marks the informer as active.
+func (c *InformerTrackingCache) GetInformer(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error) {
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return nil, errors.Wrap(err, "cannot determine group, version, and kind of supplied object")
+	}
+
+	c.mx.RLock()
+	if _, active := c.active[gvk]; active {
+		defer c.mx.RUnlock()
+		return c.Cache.GetInformer(ctx, obj, opts...)
+	}
+	c.mx.RUnlock()
+
+	c.mx.Lock()
+	defer c.mx.Unlock()
+	c.active[gvk] = true
+	return c.Cache.GetInformer(ctx, obj, opts...)
+}
+
+// GetInformerForKind is similar to GetInformer, except that it takes a
+// group-version-kind, instead of the underlying object.
+//
+// Getting an informer marks the informer as active.
+func (c *InformerTrackingCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...cache.InformerGetOption) (cache.Informer, error) {
+	c.mx.RLock()
+	if _, active := c.active[gvk]; active {
+		defer c.mx.RUnlock()
+		return c.Cache.GetInformerForKind(ctx, gvk, opts...)
+	}
+	c.mx.RUnlock()
+
+	c.mx.Lock()
+	defer c.mx.Unlock()
+	c.active[gvk] = true
+	return c.Cache.GetInformerForKind(ctx, gvk, opts...)
+}
+
+// RemoveInformer removes an informer entry and stops it if it was running.
+//
+// Removing an informer marks the informer as inactive.
+func (c *InformerTrackingCache) RemoveInformer(ctx context.Context, obj client.Object) error {
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return errors.Wrap(err, "cannot determine group, version, and kind of supplied object")
+	}
+
+	c.mx.RLock()
+	if _, active := c.active[gvk]; !active {
+		// This should only happen if RemoveInformer is called for an informer
+		// that was never started.
+		defer c.mx.RUnlock()
+		return c.Cache.RemoveInformer(ctx, obj)
+	}
+	c.mx.RUnlock()
+
+	c.mx.Lock()
+	defer c.mx.Unlock()
+	delete(c.active, gvk)
+	return c.Cache.RemoveInformer(ctx, obj)
+}
diff --git a/internal/engine/engine.go b/internal/engine/engine.go
new file mode 100644
index 000000000..f7688ab2a
--- /dev/null
+++ b/internal/engine/engine.go
@@ -0,0 +1,539 @@
+/*
+Copyright 2024 The Crossplane 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 engine manages the lifecycle of a set of controllers.
+package engine
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	kcache "k8s.io/client-go/tools/cache"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+	kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+)
+
+// A ControllerEngine manages a set of controllers that can be dynamically
+// started and stopped. It also manages a dynamic set of watches per controller,
+// and the informers that back them.
+type ControllerEngine struct {
+	// The manager of this engine's controllers. Controllers managed by the
+	// engine use the engine's client and cache, not the manager's.
+	mgr manager.Manager
+
+	// The engine must have exclusive use of these informers. All controllers
+	// managed by the engine should use these informers.
+	infs TrackingInformers
+
+	// The client used by the engine's controllers. The client must be backed by
+	// the above TrackingInformers.
+	client client.Client
+
+	log logging.Logger
+
+	// Protects everything below.
+	mx sync.RWMutex
+
+	// Running controllers, by name.
+	controllers map[string]*controller
+}
+
+// TrackingInformers is a set of Informers. It tracks which are active.
+type TrackingInformers interface {
+	cache.Informers
+	ActiveInformers() []schema.GroupVersionKind
+}
+
+// New creates a new controller engine.
+func New(mgr manager.Manager, infs TrackingInformers, c client.Client, o ...ControllerEngineOption) *ControllerEngine {
+	e := &ControllerEngine{
+		mgr:         mgr,
+		infs:        infs,
+		client:      c,
+		log:         logging.NewNopLogger(),
+		controllers: make(map[string]*controller),
+	}
+
+	for _, fn := range o {
+		fn(e)
+	}
+
+	return e
+}
+
+// An ControllerEngineOption configures a controller engine.
+type ControllerEngineOption func(*ControllerEngine)
+
+// WithLogger configures an Engine to use a logger.
+func WithLogger(l logging.Logger) ControllerEngineOption {
+	return func(e *ControllerEngine) {
+		e.log = l
+	}
+}
+
+type controller struct {
+	// The running controller.
+	ctrl kcontroller.Controller
+
+	// Called to stop the controller.
+	cancel context.CancelFunc
+
+	// Protects the below map.
+	mx sync.RWMutex
+
+	// The controller's sources, by watched GVK.
+	sources map[WatchID]*StoppableSource
+}
+
+// A WatchGarbageCollector periodically garbage collects watches.
+type WatchGarbageCollector interface {
+	GarbageCollectWatches(ctx context.Context, interval time.Duration)
+}
+
+// A NewControllerFn can start a new controller-runtime controller.
+type NewControllerFn func(name string, mgr manager.Manager, options kcontroller.Options) (kcontroller.Controller, error)
+
+// ControllerOptions configure a controller.
+type ControllerOptions struct {
+	runtime kcontroller.Options
+	nc      NewControllerFn
+	gc      WatchGarbageCollector
+}
+
+// A ControllerOption configures a controller.
+type ControllerOption func(o *ControllerOptions)
+
+// WithRuntimeOptions configures the underlying controller-runtime controller.
+func WithRuntimeOptions(ko kcontroller.Options) ControllerOption {
+	return func(o *ControllerOptions) {
+		o.runtime = ko
+	}
+}
+
+// WithWatchGarbageCollector specifies an optional garbage collector this
+// controller should use to remove unused watches.
+func WithWatchGarbageCollector(gc WatchGarbageCollector) ControllerOption {
+	return func(o *ControllerOptions) {
+		o.gc = gc
+	}
+}
+
+// WithNewControllerFn configures how the engine starts a new controller-runtime
+// controller.
+func WithNewControllerFn(fn NewControllerFn) ControllerOption {
+	return func(o *ControllerOptions) {
+		o.nc = fn
+	}
+}
+
+// GetClient gets a client backed by the controller engine's cache.
+func (e *ControllerEngine) GetClient() client.Client {
+	return e.client
+}
+
+// GetFieldIndexer returns a FieldIndexer that can be used to add indexes to the
+// controller engine's cache.
+func (e *ControllerEngine) GetFieldIndexer() client.FieldIndexer {
+	return e.infs
+}
+
+// Start a new controller.
+func (e *ControllerEngine) Start(name string, o ...ControllerOption) error {
+	e.mx.Lock()
+	defer e.mx.Unlock()
+
+	// Start is a no-op if the controller is already running.
+	if _, running := e.controllers[name]; running {
+		return nil
+	}
+
+	co := &ControllerOptions{nc: kcontroller.NewUnmanaged}
+	for _, fn := range o {
+		fn(co)
+	}
+
+	c, err := co.nc(name, e.mgr, co.runtime)
+	if err != nil {
+		return errors.Wrap(err, "cannot create new controller")
+	}
+
+	// The caller will usually be a reconcile method. We want the controller
+	// to keep running when the reconcile ends, so we create a new context
+	// instead of taking one as an argument.
+	ctx, cancel := context.WithCancel(context.Background())
+
+	go func() {
+		// Don't start the controller until the manager is elected.
+		<-e.mgr.Elected()
+
+		e.log.Debug("Starting new controller", "controller", name)
+
+		// Run the controller until its context is cancelled.
+		if err := c.Start(ctx); err != nil {
+			e.log.Info("Controller stopped with an error", "name", name, "error", err)
+
+			// Make a best effort attempt to cleanup the controller so that
+			// IsRunning will return false.
+			_ = e.Stop(ctx, name)
+			return
+		}
+
+		e.log.Debug("Stopped controller", "controller", name)
+	}()
+
+	if co.gc != nil {
+		go func() {
+			// Don't start the garbage collector until the manager is elected.
+			<-e.mgr.Elected()
+
+			e.log.Debug("Starting watch garbage collector for controller", "controller", name)
+
+			// Run the collector every minute until its context is cancelled.
+			co.gc.GarbageCollectWatches(ctx, 1*time.Minute)
+
+			e.log.Debug("Stopped watch garbage collector for controller", "controller", name)
+		}()
+	}
+
+	r := &controller{
+		ctrl:    c,
+		cancel:  cancel,
+		sources: make(map[WatchID]*StoppableSource),
+	}
+
+	e.controllers[name] = r
+
+	return nil
+}
+
+// Stop a controller.
+func (e *ControllerEngine) Stop(ctx context.Context, name string) error {
+	e.mx.Lock()
+	defer e.mx.Unlock()
+
+	c, running := e.controllers[name]
+
+	// Stop is a no-op if the controller isn't running.
+	if !running {
+		return nil
+	}
+
+	c.mx.Lock()
+	defer c.mx.Unlock()
+
+	// Stop the controller's watches.
+	for wid, w := range c.sources {
+		if err := w.Stop(ctx); err != nil {
+			c.mx.Unlock()
+			return errors.Wrapf(err, "cannot stop %q watch for %q", wid.Type, wid.GVK)
+		}
+		delete(c.sources, wid)
+		e.log.Debug("Stopped watching GVK", "controller", name, "watch-type", wid.Type, "watched-gvk", wid.GVK)
+	}
+
+	// Stop and delete the controller.
+	c.cancel()
+	delete(e.controllers, name)
+
+	e.log.Debug("Stopped controller", "controller", name)
+	return nil
+}
+
+// IsRunning returns true if the named controller is running.
+func (e *ControllerEngine) IsRunning(name string) bool {
+	e.mx.RLock()
+	defer e.mx.RUnlock()
+	_, running := e.controllers[name]
+	return running
+}
+
+// A WatchType uniquely identifies a "type" of watch - i.e. a handler and a set
+// of predicates. The controller engine uniquely identifies a Watch by its
+// (kind, watch type) tuple. The engine will only start one watch of each (kind,
+// watch type) tuple. To watch the same kind of resource multiple times, use
+// different watch types.
+type WatchType string
+
+// Common watch types.
+const (
+	WatchTypeClaim               WatchType = "Claim"
+	WatchTypeCompositeResource   WatchType = "CompositeResource"
+	WatchTypeComposedResource    WatchType = "ComposedResource"
+	WatchTypeCompositionRevision WatchType = "CompositionRevision"
+)
+
+// Watch an object.
+type Watch struct {
+	wt         WatchType
+	kind       client.Object
+	handler    handler.EventHandler
+	predicates []predicate.Predicate
+}
+
+// A WatchID uniquely identifies a watch.
+type WatchID struct {
+	Type WatchType
+	GVK  schema.GroupVersionKind
+}
+
+// WatchFor returns a Watch for the supplied kind of object. Events will be
+// handled by the supplied EventHandler, and may be filtered by the supplied
+// predicates.
+func WatchFor(kind client.Object, wt WatchType, h handler.EventHandler, p ...predicate.Predicate) Watch {
+	return Watch{kind: kind, wt: wt, handler: h, predicates: p}
+}
+
+// StartWatches instructs the named controller to start the supplied watches.
+// The controller will only start a watch if it's not already watching the type
+// of object specified by the supplied Watch. StartWatches blocks other
+// operations on the same controller if and when it starts a watch.
+func (e *ControllerEngine) StartWatches(name string, ws ...Watch) error {
+	e.mx.RLock()
+	c, running := e.controllers[name]
+	e.mx.RUnlock()
+
+	if !running {
+		return errors.Errorf("controller %q is not running", name)
+	}
+
+	// Make sure we can get GVKs for all the watches before we take locks.
+	gvks := make([]schema.GroupVersionKind, len(ws))
+	for i := range ws {
+		gvk, err := apiutil.GVKForObject(ws[i].kind, e.mgr.GetScheme())
+		if err != nil {
+			return errors.Wrapf(err, "cannot determine group, version, and kind for %T", ws[i].kind)
+		}
+		gvks[i] = gvk
+	}
+
+	// It's possible that we didn't explicitly stop a watch, but its backing
+	// informer was removed. This implicitly stops the watch by deleting its
+	// backing listener. If a watch exists but doesn't have an active informer,
+	// we want to restart the watch (and, implicitly, the informer).
+	//
+	// There's a potential race here. Another Goroutine could remove an informer
+	// between where we build the map and where we read it to check whether an
+	// informer is active. We wouldn't start a watch when we should. If the
+	// controller calls StartWatches repeatedly (e.g. an XR controller) this
+	// will eventually self-correct.
+	a := e.infs.ActiveInformers()
+	activeInformer := make(map[schema.GroupVersionKind]bool, len(a))
+	for _, gvk := range a {
+		activeInformer[gvk] = true
+	}
+
+	// Some controllers will call StartWatches on every reconcile. Most calls
+	// won't actually need to start a new watch. For example an XR controller
+	// would only need to start a new watch if an XR composed a new kind of
+	// resource that no other XR it controls already composes. So, we try to
+	// avoid taking a write lock and blocking all reconciles unless we need to.
+	c.mx.RLock()
+	start := false
+	for i, w := range ws {
+		wid := WatchID{Type: w.wt, GVK: gvks[i]}
+		// We've already created this watch and the informer backing it is still
+		// running. We don't need to create a new watch.
+		if _, watchExists := c.sources[wid]; watchExists && activeInformer[wid.GVK] {
+			e.log.Debug("Watch exists for GVK, not starting a new one", "controller", name, "watch-type", wid.Type, "watched-gvk", wid.GVK)
+			continue
+		}
+		// There's at least one watch to start.
+		start = true
+		break
+	}
+	c.mx.RUnlock()
+
+	// Nothing to start.
+	if !start {
+		return nil
+	}
+
+	// We have at least one watch to start - take the write lock. It's possible
+	// another Goroutine updated this controller's watches since we released the
+	// read lock, so we compute everything again.
+	c.mx.Lock()
+	defer c.mx.Unlock()
+
+	// Start new sources.
+	for i, w := range ws {
+		wid := WatchID{Type: w.wt, GVK: gvks[i]}
+
+		// We've already created this watch and the informer backing it is still
+		// running. We don't need to create a new watch. We don't debug log this
+		// one - we'll have logged it above unless the watch was added between
+		// releasing the read lock and taking the write lock.
+		if _, watchExists := c.sources[wid]; watchExists && activeInformer[wid.GVK] {
+			continue
+		}
+
+		// The controller's Watch method just calls the StoppableSource's Start
+		// method, passing in its private work queue as an argument. This will
+		// start an informer for the watched kind if there isn't one running
+		// already.
+		//
+		// The watch will stop sending events when either the source is stopped,
+		// or its backing informer is stopped. The controller's work queue will
+		// stop processing events when the controller is stopped.
+		src := NewStoppableSource(e.infs, w.kind, w.handler, w.predicates...)
+		if err := c.ctrl.Watch(src); err != nil {
+			return errors.Wrapf(err, "cannot start %q watch for %q", wid.Type, wid.GVK)
+		}
+
+		// Record that we're now running this source.
+		c.sources[wid] = src
+
+		e.log.Debug("Started watching GVK", "controller", name, "watch-type", wid.Type, "watched-gvk", wid.GVK)
+	}
+
+	return nil
+}
+
+// GetWatches returns the active watches for the supplied controller.
+func (e *ControllerEngine) GetWatches(name string) ([]WatchID, error) {
+	e.mx.RLock()
+	c, running := e.controllers[name]
+	e.mx.RUnlock()
+
+	if !running {
+		return nil, errors.Errorf("controller %q is not running", name)
+	}
+
+	c.mx.RLock()
+	defer c.mx.RUnlock()
+
+	out := make([]WatchID, 0, len(c.sources))
+	for wid := range c.sources {
+		out = append(out, wid)
+	}
+	return out, nil
+}
+
+// StopWatches stops the supplied watches. StopWatches blocks other operations
+// on the same controller if and when it stops a watch. It returns the number of
+// watches that it successfully stopped.
+func (e *ControllerEngine) StopWatches(ctx context.Context, name string, ws ...WatchID) (int, error) {
+	e.mx.RLock()
+	c, running := e.controllers[name]
+	e.mx.RUnlock()
+
+	if !running {
+		return 0, errors.Errorf("controller %q is not running", name)
+	}
+
+	// Don't take the write lock if we want to keep all watches.
+	c.mx.RLock()
+	stop := false
+	for _, wid := range ws {
+		if _, watchExists := c.sources[wid]; watchExists {
+			stop = true
+			break
+		}
+	}
+	c.mx.RUnlock()
+
+	if !stop {
+		return 0, nil
+	}
+
+	// We have at least one watch to stop - take the write lock. It's possible
+	// another Goroutine updated this controller's watches since we released the
+	// read lock, so we compute everything again.
+	c.mx.Lock()
+	defer c.mx.Unlock()
+
+	stopped := 0
+	for _, wid := range ws {
+		w, watchExists := c.sources[wid]
+		if !watchExists {
+			continue
+		}
+		if err := w.Stop(ctx); err != nil {
+			return stopped, errors.Wrapf(err, "cannot stop %q watch for %q", wid.Type, wid.GVK)
+		}
+		delete(c.sources, wid)
+		e.log.Debug("Stopped watching GVK", "controller", name, "watch-type", wid.Type, "watched-gvk", wid.GVK)
+		stopped++
+	}
+
+	return stopped, nil
+}
+
+// GarbageCollectCustomResourceInformers garbage collects informers for custom
+// resources (e.g. Crossplane XRs, claims and composed resources) when the CRD
+// that defines them is deleted. The garbage collector runs until the supplied
+// context is cancelled.
+func (e *ControllerEngine) GarbageCollectCustomResourceInformers(ctx context.Context) error {
+	i, err := e.infs.GetInformer(ctx, &extv1.CustomResourceDefinition{})
+	if err != nil {
+		return errors.Wrap(err, "cannot get informer for CustomResourceDefinitions")
+	}
+
+	h, err := i.AddEventHandler(kcache.ResourceEventHandlerFuncs{
+		DeleteFunc: func(obj interface{}) {
+			o := obj
+			if fsu, ok := obj.(kcache.DeletedFinalStateUnknown); ok {
+				o = fsu.Obj
+			}
+			crd, ok := o.(*extv1.CustomResourceDefinition)
+			if !ok {
+				// This should never happen.
+				return
+			}
+
+			for _, v := range crd.Spec.Versions {
+				gvk := schema.GroupVersionKind{
+					Group:   crd.Spec.Group,
+					Kind:    crd.Spec.Names.Kind,
+					Version: v.Name,
+				}
+
+				u := &unstructured.Unstructured{}
+				u.SetGroupVersionKind(gvk)
+
+				if err := e.infs.RemoveInformer(ctx, u); err != nil {
+					e.log.Info("Cannot remove informer for type defined by deleted CustomResourceDefinition", "crd", crd.GetName(), "gvk", gvk)
+					continue
+				}
+
+				e.log.Debug("Removed informer for type defined by deleted CustomResourceDefinition", "crd", crd.GetName(), "gvk", gvk)
+			}
+		},
+	})
+	if err != nil {
+		return errors.Wrap(err, "cannot add garbage collector event handler to CustomResourceDefinition informer")
+	}
+
+	go func() {
+		<-ctx.Done()
+		if err := i.RemoveEventHandler(h); err != nil {
+			e.log.Info("Cannot remove garbage collector event handler from CustomResourceDefinition informer")
+		}
+	}()
+
+	return nil
+}
diff --git a/internal/engine/source.go b/internal/engine/source.go
new file mode 100644
index 000000000..e04aa3ca3
--- /dev/null
+++ b/internal/engine/source.go
@@ -0,0 +1,195 @@
+/*
+Copyright 2024 The Crossplane 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 engine
+
+import (
+	"context"
+
+	kcache "k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/util/workqueue"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/source"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+)
+
+var _ source.Source = &StoppableSource{}
+
+// NewStoppableSource returns a new watch source that can be stopped.
+func NewStoppableSource(infs cache.Informers, t client.Object, h handler.EventHandler, ps ...predicate.Predicate) *StoppableSource {
+	return &StoppableSource{infs: infs, Type: t, handler: h, predicates: ps}
+}
+
+// A StoppableSource is a controller-runtime watch source that can be stopped.
+type StoppableSource struct {
+	infs cache.Informers
+
+	Type       client.Object
+	handler    handler.EventHandler
+	predicates []predicate.Predicate
+
+	reg kcache.ResourceEventHandlerRegistration
+}
+
+// Start is internal and should be called only by the Controller to register
+// an EventHandler with the Informer to enqueue reconcile.Requests.
+func (s *StoppableSource) Start(ctx context.Context, q workqueue.RateLimitingInterface) error {
+	i, err := s.infs.GetInformer(ctx, s.Type, cache.BlockUntilSynced(true))
+	if err != nil {
+		return errors.Wrapf(err, "cannot get informer for %T", s.Type)
+	}
+
+	reg, err := i.AddEventHandler(NewEventHandler(ctx, q, s.handler, s.predicates...).HandlerFuncs())
+	if err != nil {
+		return errors.Wrapf(err, "cannot add event handler")
+	}
+	s.reg = reg
+
+	return nil
+}
+
+// Stop removes the EventHandler from the source's Informer. The Informer will
+// stop sending events to the source.
+func (s *StoppableSource) Stop(ctx context.Context) error {
+	if s.reg == nil {
+		return nil
+	}
+
+	i, err := s.infs.GetInformer(ctx, s.Type)
+	if err != nil {
+		return errors.Wrapf(err, "cannot get informer for %T", s.Type)
+	}
+
+	if err := i.RemoveEventHandler(s.reg); err != nil {
+		return errors.Wrap(err, "cannot remove event handler")
+	}
+
+	s.reg = nil
+	return nil
+}
+
+// NewEventHandler creates a new EventHandler.
+func NewEventHandler(ctx context.Context, q workqueue.RateLimitingInterface, h handler.EventHandler, ps ...predicate.Predicate) *EventHandler {
+	return &EventHandler{
+		ctx:        ctx,
+		handler:    h,
+		queue:      q,
+		predicates: ps,
+	}
+}
+
+// An EventHandler converts a controller-runtime handler and predicates into a
+// client-go ResourceEventHandler. It's a stripped down version of
+// controller-runtime's internal implementation.
+// https://github.com/kubernetes-sigs/controller-runtime/blob/v0.18.2/pkg/internal/source/event_handler.go#L35
+type EventHandler struct {
+	ctx context.Context //nolint:containedctx // Kept for compatibility with controller-runtime.
+
+	handler    handler.EventHandler
+	queue      workqueue.RateLimitingInterface
+	predicates []predicate.Predicate
+}
+
+// HandlerFuncs converts EventHandler to a ResourceEventHandlerFuncs.
+func (e *EventHandler) HandlerFuncs() kcache.ResourceEventHandlerFuncs {
+	return kcache.ResourceEventHandlerFuncs{
+		AddFunc:    e.OnAdd,
+		UpdateFunc: e.OnUpdate,
+		DeleteFunc: e.OnDelete,
+	}
+}
+
+// OnAdd creates CreateEvent and calls Create on EventHandler.
+func (e *EventHandler) OnAdd(obj interface{}) {
+	o, ok := obj.(client.Object)
+	if !ok {
+		return
+	}
+
+	c := event.CreateEvent{Object: o}
+	for _, p := range e.predicates {
+		if !p.Create(c) {
+			return
+		}
+	}
+
+	ctx, cancel := context.WithCancel(e.ctx)
+	defer cancel()
+	e.handler.Create(ctx, c, e.queue)
+}
+
+// OnUpdate creates UpdateEvent and calls Update on EventHandler.
+func (e *EventHandler) OnUpdate(oldObj, newObj interface{}) {
+	o, ok := oldObj.(client.Object)
+	if !ok {
+		return
+	}
+
+	n, ok := newObj.(client.Object)
+	if !ok {
+		return
+	}
+
+	u := event.UpdateEvent{ObjectOld: o, ObjectNew: n}
+
+	for _, p := range e.predicates {
+		if !p.Update(u) {
+			return
+		}
+	}
+
+	ctx, cancel := context.WithCancel(e.ctx)
+	defer cancel()
+	e.handler.Update(ctx, u, e.queue)
+}
+
+// OnDelete creates DeleteEvent and calls Delete on EventHandler.
+func (e *EventHandler) OnDelete(obj interface{}) {
+	var d event.DeleteEvent
+
+	switch o := obj.(type) {
+	case client.Object:
+		d = event.DeleteEvent{Object: o}
+
+	// Deal with tombstone events by pulling the object out. Tombstone events
+	// wrap the object in a DeleteFinalStateUnknown struct, so the object needs
+	// to be pulled out.
+	case kcache.DeletedFinalStateUnknown:
+		wrapped, ok := o.Obj.(client.Object)
+		if !ok {
+			return
+		}
+		d = event.DeleteEvent{DeleteStateUnknown: true, Object: wrapped}
+
+	default:
+		return
+	}
+
+	for _, p := range e.predicates {
+		if !p.Delete(d) {
+			return
+		}
+	}
+
+	ctx, cancel := context.WithCancel(e.ctx)
+	defer cancel()
+	e.handler.Delete(ctx, d, e.queue)
+}
diff --git a/internal/names/generate.go b/internal/names/generate.go
index 721511ff9..33cf591ab 100644
--- a/internal/names/generate.go
+++ b/internal/names/generate.go
@@ -84,7 +84,7 @@ func (r *nameGenerator) GenerateName(ctx context.Context, cd resource.Object) er
 	//    locally. To reduce that risk even further the caller must employ a
 	//    conflict recovery mechanism.
 	maxTries := 10
-	for i := 0; i < maxTries; i++ {
+	for range maxTries {
 		name := r.namer.GenerateName(cd.GetGenerateName())
 		obj := composite.Unstructured{}
 		obj.SetGroupVersionKind(cd.GetObjectKind().GroupVersionKind())
diff --git a/internal/validation/apiextensions/v1/composition/handler.go b/internal/validation/apiextensions/v1/composition/handler.go
index 66984b7a0..21dc9328c 100644
--- a/internal/validation/apiextensions/v1/composition/handler.go
+++ b/internal/validation/apiextensions/v1/composition/handler.go
@@ -189,7 +189,6 @@ func (v *validator) getNeededCRDs(ctx context.Context, comp *v1.Composition) (ma
 	// Get schema for all Managed Resource Definitions defined by
 	// comp.Spec.Resources.
 	for _, res := range comp.Spec.Resources {
-		res := res
 		gvk, err := composition.GetBaseObjectGVK(&res)
 		if err != nil {
 			return nil, []error{err}
diff --git a/internal/xpkg/scheme.go b/internal/xpkg/scheme.go
index c046b45d2..cb7efc73f 100644
--- a/internal/xpkg/scheme.go
+++ b/internal/xpkg/scheme.go
@@ -77,7 +77,6 @@ func TryConvert(obj runtime.Object, candidates ...conversion.Hub) (runtime.Objec
 	}
 
 	for _, c := range candidates {
-		c := c
 		if err := cvt.ConvertTo(c); err == nil {
 			return c, true
 		}
diff --git a/pkg/validation/apiextensions/v1/composition/patches.go b/pkg/validation/apiextensions/v1/composition/patches.go
index 6768096c7..76a918126 100644
--- a/pkg/validation/apiextensions/v1/composition/patches.go
+++ b/pkg/validation/apiextensions/v1/composition/patches.go
@@ -327,7 +327,6 @@ func validateTransformsChainIOTypes(transforms []v1.Transform, fromType xpschema
 		return "", field.InternalError(field.NewPath("transforms"), err)
 	}
 	for i, transform := range transforms {
-		transform := transform
 		err := IsValidInputForTransform(&transform, inputType)
 		if err != nil && inputType != "" {
 			return "", field.Invalid(field.NewPath("transforms").Index(i), transform, err.Error())



Test patch:
diff --git a/internal/controller/apiextensions/claim/reconciler_test.go b/internal/controller/apiextensions/claim/reconciler_test.go
index b27d52eca..f8a1ff899 100644
--- a/internal/controller/apiextensions/claim/reconciler_test.go
+++ b/internal/controller/apiextensions/claim/reconciler_test.go
@@ -28,7 +28,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -36,7 +35,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
@@ -47,10 +45,10 @@ func TestReconcile(t *testing.T) {
 	now := metav1.Now()
 
 	type args struct {
-		mgr  manager.Manager
-		of   resource.CompositeClaimKind
-		with resource.CompositeKind
-		opts []ReconcilerOption
+		client client.Client
+		of     resource.CompositeClaimKind
+		with   resource.CompositeKind
+		opts   []ReconcilerOption
 	}
 	type want struct {
 		r   reconcile.Result
@@ -65,11 +63,8 @@ func TestReconcile(t *testing.T) {
 		"ClaimNotFound": {
 			reason: "We should not return an error if the composite resource was not found.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
-					}),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
 				},
 			},
 			want: want{
@@ -79,11 +74,8 @@ func TestReconcile(t *testing.T) {
 		"GetClaimError": {
 			reason: "We should return any error we encounter getting the claim.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(errBoom),
-					}),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
 				},
 			},
 			want: want{
@@ -94,19 +86,16 @@ func TestReconcile(t *testing.T) {
 		"ReconciliationPaused": {
 			reason: `If a claim has the pause annotation with value "true" we should stop reconciling and not requeue.`,
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							obj.(*claim.Unstructured).SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
-							cm.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						obj.(*claim.Unstructured).SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
+						cm.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+					})),
 				},
 			},
 			want: want{
@@ -116,21 +105,20 @@ func TestReconcile(t *testing.T) {
 		"ReconciliationUnpaused": {
 			reason: "If a claim has the ReconcilePaused status condition but no paused annotation, the condition should change to ReconcileSuccess.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							// This claim was paused.
-							obj.(*claim.Unstructured).SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that our synced status condition changed
-							// from Paused to ReconcileSuccess.
-							cm.SetConditions(xpv1.ReconcileSuccess())
-							cm.SetConditions(Waiting())
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						// This claim was paused.
+						obj.(*claim.Unstructured).SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that our synced status condition changed
+						// from Paused to ReconcileSuccess.
+						cm.SetConditions(xpv1.ReconcileSuccess())
+						cm.SetConditions(Waiting())
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -147,27 +135,24 @@ func TestReconcile(t *testing.T) {
 		"GetCompositeError": {
 			reason: "The reconcile should fail if we can't get the XR, unless it wasn't found.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							case *composite.Unstructured:
-								// Return an error getting the XR.
-								return errBoom
-							}
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errGetComposite)))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						case *composite.Unstructured:
+							// Return an error getting the XR.
+							return errBoom
+						}
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errGetComposite)))
+					})),
 				},
 			},
 			want: want{
@@ -177,29 +162,26 @@ func TestReconcile(t *testing.T) {
 		"CompositeAlreadyBoundError": {
 			reason: "The reconcile should fail if the referenced XR is bound to another claim",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							case *composite.Unstructured:
-								// This XR was created, and references another
-								//  claim.
-								o.SetCreationTimestamp(now)
-								o.SetClaimReference(&claim.Reference{Name: "some-other-claim"})
-							}
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							cm.SetConditions(xpv1.ReconcileError(errors.Errorf(errFmtUnbound, "", "some-other-claim")))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						case *composite.Unstructured:
+							// This XR was created, and references another
+							//  claim.
+							o.SetCreationTimestamp(now)
+							o.SetClaimReference(&claim.Reference{Name: "some-other-claim"})
+						}
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						cm.SetConditions(xpv1.ReconcileError(errors.Errorf(errFmtUnbound, "", "some-other-claim")))
+					})),
 				},
 			},
 			want: want{
@@ -209,31 +191,30 @@ func TestReconcile(t *testing.T) {
 		"DeleteCompositeError": {
 			reason: "We should not try to delete if the resource is already gone.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								o.SetDeletionTimestamp(&now)
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							case *composite.Unstructured:
-								// Pretend the XR exists.
-								o.SetCreationTimestamp(now)
-							}
-							return nil
-						}),
-						MockDelete: test.NewMockDeleteFn(errBoom),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetDeletionTimestamp(&now)
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							cm.SetConditions(xpv1.Deleting())
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errDeleteComposite)))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							o.SetDeletionTimestamp(&now)
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						case *composite.Unstructured:
+							// Pretend the XR exists.
+							o.SetCreationTimestamp(now)
+						}
+						return nil
 					}),
+					MockDelete: test.NewMockDeleteFn(errBoom),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetDeletionTimestamp(&now)
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						cm.SetConditions(xpv1.Deleting())
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errDeleteComposite)))
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -246,21 +227,20 @@ func TestReconcile(t *testing.T) {
 		"UnpublishConnectionDetailsError": {
 			reason: "The reconcile should fail if we can't unpublish the claim's connection details.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							obj.(*claim.Unstructured).SetDeletionTimestamp(&now)
-							return nil
-						}),
-						MockDelete: test.NewMockDeleteFn(nil),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetDeletionTimestamp(&now)
-							cm.SetConditions(xpv1.Deleting())
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errDeleteCDs)))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						obj.(*claim.Unstructured).SetDeletionTimestamp(&now)
+						return nil
 					}),
+					MockDelete: test.NewMockDeleteFn(nil),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetDeletionTimestamp(&now)
+						cm.SetConditions(xpv1.Deleting())
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errDeleteCDs)))
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithConnectionUnpublisher(ConnectionUnpublisherFn(func(_ context.Context, _ resource.LocalConnectionSecretOwner, _ managed.ConnectionDetails) error {
 						return errBoom
 					})),
@@ -273,21 +253,20 @@ func TestReconcile(t *testing.T) {
 		"RemoveFinalizerError": {
 			reason: "The reconcile should fail if we can't remove the claim's finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							obj.(*claim.Unstructured).SetDeletionTimestamp(&now)
-							return nil
-						}),
-						MockDelete: test.NewMockDeleteFn(nil),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetDeletionTimestamp(&now)
-							cm.SetConditions(xpv1.Deleting())
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errRemoveFinalizer)))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						obj.(*claim.Unstructured).SetDeletionTimestamp(&now)
+						return nil
 					}),
+					MockDelete: test.NewMockDeleteFn(nil),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetDeletionTimestamp(&now)
+						cm.SetConditions(xpv1.Deleting())
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errRemoveFinalizer)))
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return errBoom },
 					}),
@@ -300,21 +279,20 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulDelete": {
 			reason: "We should not requeue if we successfully delete the resource.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							obj.(*claim.Unstructured).SetDeletionTimestamp(&now)
-							return nil
-						}),
-						MockDelete: test.NewMockDeleteFn(nil),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetDeletionTimestamp(&now)
-							cm.SetConditions(xpv1.Deleting())
-							cm.SetConditions(xpv1.ReconcileSuccess())
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						obj.(*claim.Unstructured).SetDeletionTimestamp(&now)
+						return nil
 					}),
+					MockDelete: test.NewMockDeleteFn(nil),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetDeletionTimestamp(&now)
+						cm.SetConditions(xpv1.Deleting())
+						cm.SetConditions(xpv1.ReconcileSuccess())
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -327,28 +305,27 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulForegroundDelete": {
 			reason: "We should requeue if we successfully delete the bound composite resource using Foreground deletion",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								o.SetDeletionTimestamp(&now)
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-								// We want to foreground delete.
-								fg := xpv1.CompositeDeleteForeground
-								o.SetCompositeDeletePolicy(&fg)
-							case *composite.Unstructured:
-								// Pretend the XR exists and is bound.
-								o.SetCreationTimestamp(now)
-								o.SetClaimReference(&claim.Reference{})
-							}
-							return nil
-						}),
-						MockDelete: test.NewMockDeleteFn(nil),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							o.SetDeletionTimestamp(&now)
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+							// We want to foreground delete.
+							fg := xpv1.CompositeDeleteForeground
+							o.SetCompositeDeletePolicy(&fg)
+						case *composite.Unstructured:
+							// Pretend the XR exists and is bound.
+							o.SetCreationTimestamp(now)
+							o.SetClaimReference(&claim.Reference{})
+						}
+						return nil
 					}),
+					MockDelete: test.NewMockDeleteFn(nil),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -361,40 +338,38 @@ func TestReconcile(t *testing.T) {
 		"ForegroundDeleteWaitForCompositeDeletion": {
 			reason: "We should requeue if we successfully deleted the bound composite resource using Foreground deletion and it has not yet been deleted",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								o.SetDeletionTimestamp(&now)
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-								// We want to foreground delete.
-								fg := xpv1.CompositeDeleteForeground
-								o.SetCompositeDeletePolicy(&fg)
-							case *composite.Unstructured:
-								// Pretend the XR exists and is bound, but is
-								// being deleted.
-								o.SetCreationTimestamp(now)
-								o.SetDeletionTimestamp(&now)
-								o.SetClaimReference(&claim.Reference{})
-							}
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							o.SetDeletionTimestamp(&now)
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
 							// We want to foreground delete.
 							fg := xpv1.CompositeDeleteForeground
-							cm.SetCompositeDeletePolicy(&fg)
+							o.SetCompositeDeletePolicy(&fg)
+						case *composite.Unstructured:
+							// Pretend the XR exists and is bound, but is
+							// being deleted.
+							o.SetCreationTimestamp(now)
+							o.SetDeletionTimestamp(&now)
+							o.SetClaimReference(&claim.Reference{})
+						}
+						return nil
+					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						// We want to foreground delete.
+						fg := xpv1.CompositeDeleteForeground
+						cm.SetCompositeDeletePolicy(&fg)
 
-							// Check that we set our status condition.
-							cm.SetDeletionTimestamp(&now)
-							cm.SetConditions(xpv1.Deleting())
-						})),
-					},
-					),
+						// Check that we set our status condition.
+						cm.SetDeletionTimestamp(&now)
+						cm.SetConditions(xpv1.Deleting())
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -407,15 +382,14 @@ func TestReconcile(t *testing.T) {
 		"AddFinalizerError": {
 			reason: "We should fail the reconcile if we can't add the claim's finalizer",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errAddFinalizer)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errAddFinalizer)))
-						})),
-					}),
 					WithClaimFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return errBoom },
 					}),
@@ -428,15 +402,14 @@ func TestReconcile(t *testing.T) {
 		"SyncCompositeError": {
 			reason: "We should fail the reconcile if we can't bind and sync the claim with a composite resource",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errSync)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errSync)))
-						})),
-					}),
 					WithClaimFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -450,31 +423,30 @@ func TestReconcile(t *testing.T) {
 		"CompositeNotReady": {
 			reason: "We should return early if the bound composite resource is not yet ready",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							case *composite.Unstructured:
-								// Pretend the XR exists and is bound, but is
-								// still being created.
-								o.SetCreationTimestamp(now)
-								o.SetClaimReference(&claim.Reference{})
-								o.SetConditions(xpv1.Creating())
-							}
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							cm.SetConditions(xpv1.ReconcileSuccess())
-							cm.SetConditions(Waiting())
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						case *composite.Unstructured:
+							// Pretend the XR exists and is bound, but is
+							// still being created.
+							o.SetCreationTimestamp(now)
+							o.SetClaimReference(&claim.Reference{})
+							o.SetConditions(xpv1.Creating())
+						}
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						cm.SetConditions(xpv1.ReconcileSuccess())
+						cm.SetConditions(Waiting())
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -488,29 +460,28 @@ func TestReconcile(t *testing.T) {
 		"PropagateConnectionError": {
 			reason: "We should fail the reconcile if we can't propagate the bound XR's connection details",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							case *composite.Unstructured:
-								// Pretend the XR exists and is available.
-								o.SetCreationTimestamp(now)
-								o.SetClaimReference(&claim.Reference{})
-								o.SetConditions(xpv1.Available())
-							}
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errPropagateCDs)))
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						case *composite.Unstructured:
+							// Pretend the XR exists and is available.
+							o.SetCreationTimestamp(now)
+							o.SetClaimReference(&claim.Reference{})
+							o.SetConditions(xpv1.Available())
+						}
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						cm.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errPropagateCDs)))
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -527,31 +498,30 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulReconcile": {
 			reason: "We should not requeue if we successfully synced the composite resource and propagated its connection details",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-							switch o := obj.(type) {
-							case *claim.Unstructured:
-								// We won't try to get an XR unless the claim
-								// references one.
-								o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							case *composite.Unstructured:
-								// Pretend the XR exists and is available.
-								o.SetCreationTimestamp(now)
-								o.SetClaimReference(&claim.Reference{})
-								o.SetConditions(xpv1.Available())
-							}
-							return nil
-						}),
-						MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
-							// Check that we set our status condition.
-							cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
-							cm.SetConnectionDetailsLastPublishedTime(&now)
-							cm.SetConditions(xpv1.ReconcileSuccess())
-							cm.SetConditions(xpv1.Available())
-						})),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+						switch o := obj.(type) {
+						case *claim.Unstructured:
+							// We won't try to get an XR unless the claim
+							// references one.
+							o.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						case *composite.Unstructured:
+							// Pretend the XR exists and is available.
+							o.SetCreationTimestamp(now)
+							o.SetClaimReference(&claim.Reference{})
+							o.SetConditions(xpv1.Available())
+						}
+						return nil
 					}),
+					MockStatusUpdate: WantClaim(t, NewClaim(func(cm *claim.Unstructured) {
+						// Check that we set our status condition.
+						cm.SetResourceReference(&corev1.ObjectReference{Name: "cool-composite"})
+						cm.SetConnectionDetailsLastPublishedTime(&now)
+						cm.SetConditions(xpv1.ReconcileSuccess())
+						cm.SetConditions(xpv1.Available())
+					})),
+				},
+				opts: []ReconcilerOption{
 					WithClaimFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil },
 					}),
@@ -569,7 +539,7 @@ func TestReconcile(t *testing.T) {
 
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
-			r := NewReconciler(tc.args.mgr, tc.args.of, tc.args.with, tc.args.opts...)
+			r := NewReconciler(tc.args.client, tc.args.of, tc.args.with, tc.args.opts...)
 
 			got, err := r.Reconcile(context.Background(), reconcile.Request{})
 			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
diff --git a/internal/controller/apiextensions/composite/composition_functions_test.go b/internal/controller/apiextensions/composite/composition_functions_test.go
index 0c5d524d0..062c71f4b 100644
--- a/internal/controller/apiextensions/composite/composition_functions_test.go
+++ b/internal/controller/apiextensions/composite/composition_functions_test.go
@@ -406,11 +406,12 @@ func TestFunctionCompose(t *testing.T) {
 				kube: &test.MockClient{
 					MockPatch: test.NewMockPatchFn(nil, func(obj client.Object) error {
 						// We only want to return an error for the XR.
-						u := obj.(*kunstructured.Unstructured)
-						if u.GetKind() == "CoolComposed" {
-							return nil
+						switch obj.(type) {
+						case *composite.Unstructured:
+							return errBoom
+						default:
 						}
-						return errBoom
+						return nil
 					}),
 				},
 				r: FunctionRunnerFn(func(_ context.Context, _ string, _ *v1beta1.RunFunctionRequest) (rsp *v1beta1.RunFunctionResponse, err error) {
@@ -505,9 +506,10 @@ func TestFunctionCompose(t *testing.T) {
 					MockPatch: test.NewMockPatchFn(nil, func(obj client.Object) error {
 						// We only want to return an error if we're patching a
 						// composed resource.
-						u := obj.(*kunstructured.Unstructured)
-						if u.GetKind() == "UncoolComposed" {
+						switch obj.(type) {
+						case *composed.Unstructured:
 							return errBoom
+						default:
 						}
 						return nil
 					}),
diff --git a/internal/controller/apiextensions/composite/reconciler_test.go b/internal/controller/apiextensions/composite/reconciler_test.go
index 5c42c3715..6d68d15c1 100644
--- a/internal/controller/apiextensions/composite/reconciler_test.go
+++ b/internal/controller/apiextensions/composite/reconciler_test.go
@@ -18,7 +18,6 @@ package composite
 
 import (
 	"context"
-	"reflect"
 	"testing"
 	"time"
 
@@ -27,27 +26,22 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/util/workqueue"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	runtimeevent "sigs.k8s.io/controller-runtime/pkg/event"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 	"github.com/crossplane/crossplane-runtime/pkg/event"
-	"github.com/crossplane/crossplane-runtime/pkg/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+	"github.com/crossplane/crossplane/internal/engine"
 )
 
 var _ Composer = ComposerSelectorFn(func(_ *v1.CompositionMode) Composer { return nil })
@@ -57,9 +51,9 @@ func TestReconcile(t *testing.T) {
 	cd := managed.ConnectionDetails{"a": []byte("b")}
 
 	type args struct {
-		mgr  manager.Manager
-		of   resource.CompositeKind
-		opts []ReconcilerOption
+		client client.Client
+		of     resource.CompositeKind
+		opts   []ReconcilerOption
 	}
 	type want struct {
 		r   reconcile.Result
@@ -76,11 +70,8 @@ func TestReconcile(t *testing.T) {
 		"CompositeResourceNotFound": {
 			reason: "We should not return an error if the composite resource was not found.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
-					}),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
 				},
 			},
 			want: want{
@@ -90,11 +81,8 @@ func TestReconcile(t *testing.T) {
 		"GetCompositeResourceError": {
 			reason: "We should return error encountered while getting the composite resource.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(errBoom),
-					}),
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
 				},
 			},
 			want: want{
@@ -104,17 +92,16 @@ func TestReconcile(t *testing.T) {
 		"UnpublishConnectionError": {
 			reason: "We should return any error encountered while unpublishing connection details.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetDeletionTimestamp(&now)
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(want resource.Composite) {
+						want.SetDeletionTimestamp(&now)
+						want.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(errBoom, errUnpublish)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetDeletionTimestamp(&now)
-						})),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(want resource.Composite) {
-							want.SetDeletionTimestamp(&now)
-							want.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(errBoom, errUnpublish)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithConnectionPublishers(managed.ConnectionPublisherFns{
 						UnpublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ managed.ConnectionDetails) error {
@@ -130,17 +117,16 @@ func TestReconcile(t *testing.T) {
 		"RemoveFinalizerError": {
 			reason: "We should return any error encountered while removing finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetDeletionTimestamp(&now)
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetDeletionTimestamp(&now)
+						cr.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(errBoom, errRemoveFinalizer)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetDeletionTimestamp(&now)
-						})),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetDeletionTimestamp(&now)
-							cr.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(errBoom, errRemoveFinalizer)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error {
 							return errBoom
@@ -160,17 +146,16 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulDelete": {
 			reason: "We should return no error when deleted successfully.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetDeletionTimestamp(&now)
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetDeletionTimestamp(&now)
+						cr.SetConditions(xpv1.Deleting(), xpv1.ReconcileSuccess())
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetDeletionTimestamp(&now)
-						})),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetDeletionTimestamp(&now)
-							cr.SetConditions(xpv1.Deleting(), xpv1.ReconcileSuccess())
-						})),
-					}),
 					WithCompositeFinalizer(resource.FinalizerFns{
 						RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error {
 							return nil
@@ -190,14 +175,13 @@ func TestReconcile(t *testing.T) {
 		"AddFinalizerError": {
 			reason: "We should return any error encountered while adding finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errAddFinalizer)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errAddFinalizer)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.FinalizerFns{
 						AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
 							return errBoom
@@ -212,14 +196,13 @@ func TestReconcile(t *testing.T) {
 		"SelectCompositionError": {
 			reason: "We should return any error encountered while selecting a composition.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errSelectComp)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errSelectComp)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, _ resource.Composite) error {
 						return errBoom
@@ -233,15 +216,14 @@ func TestReconcile(t *testing.T) {
 		"FetchCompositionError": {
 			reason: "We should return any error encountered while fetching a composition.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errFetchComp)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errFetchComp)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -259,15 +241,14 @@ func TestReconcile(t *testing.T) {
 		"ValidateCompositionError": {
 			reason: "We should return any error encountered while validating our Composition.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errValidate)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errValidate)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -288,15 +269,14 @@ func TestReconcile(t *testing.T) {
 		"ConfigureCompositeError": {
 			reason: "We should return any error encountered while configuring the composite resource.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errConfigure)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errConfigure)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -318,12 +298,11 @@ func TestReconcile(t *testing.T) {
 		"SelectEnvironmentError": {
 			reason: "We should return any error encountered while selecting the environment.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet:          test.NewMockGetFn(nil),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet:          test.NewMockGetFn(nil),
-						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, _ resource.Composite) error {
 						return nil
@@ -346,12 +325,11 @@ func TestReconcile(t *testing.T) {
 		"FetchEnvironmentError": {
 			reason: "We should requeue on any error encountered while fetching the environment.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet:          test.NewMockGetFn(nil),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet:          test.NewMockGetFn(nil),
-						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, _ resource.Composite) error {
 						return nil
@@ -374,15 +352,14 @@ func TestReconcile(t *testing.T) {
 		"ComposeResourcesError": {
 			reason: "We should return any error encountered while composing resources.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errCompose)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errCompose)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -407,15 +384,14 @@ func TestReconcile(t *testing.T) {
 		"PublishConnectionDetailsError": {
 			reason: "We should return any error encountered while publishing connection details.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errPublish)))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileError(errors.Wrap(errBoom, errPublish)))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -445,15 +421,14 @@ func TestReconcile(t *testing.T) {
 		"CompositionWarnings": {
 			reason: "We should not requeue if our Composer returned warning events.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(xr resource.Composite) {
+						xr.SetCompositionReference(&corev1.ObjectReference{})
+						xr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(xr resource.Composite) {
-							xr.SetCompositionReference(&corev1.ObjectReference{})
-							xr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -488,15 +463,14 @@ func TestReconcile(t *testing.T) {
 		"ComposedResourcesNotReady": {
 			reason: "We should requeue if any of our composed resources are not yet ready.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Creating().WithMessage("Unready resources: cat, cow, elephant, and 1 more"))
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Creating().WithMessage("Unready resources: cat, cow, elephant, and 1 more"))
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -555,16 +529,24 @@ func TestReconcile(t *testing.T) {
 		"ComposedResourcesReady": {
 			reason: "We should requeue after our poll interval if all of our composed resources are ready.",
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetResourceReferences([]corev1.ObjectReference{{
+							APIVersion: "example.org/v1",
+							Kind:       "ComposedResource",
+						}})
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+						cr.SetResourceReferences([]corev1.ObjectReference{{
+							APIVersion: "example.org/v1",
+							Kind:       "ComposedResource",
+						}})
+						cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
+						cr.SetConnectionDetailsLastPublishedTime(&now)
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: test.NewMockGetFn(nil),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-							cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
-							cr.SetConnectionDetailsLastPublishedTime(&now)
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -592,6 +574,19 @@ func TestReconcile(t *testing.T) {
 							return true, nil
 						},
 					}),
+					WithWatchStarter("cool-controller", nil, WatchStarterFn(func(_ string, ws ...engine.Watch) error {
+						cd := composed.New(composed.FromReference(corev1.ObjectReference{
+							APIVersion: "example.org/v1",
+							Kind:       "ComposedResource",
+						}))
+						want := []engine.Watch{engine.WatchFor(cd, engine.WatchTypeComposedResource, nil)}
+
+						if diff := cmp.Diff(want, ws, cmp.AllowUnexported(engine.Watch{})); diff != "" {
+							t.Errorf("StartWatches(...): -want, +got:\n%s", diff)
+						}
+
+						return nil
+					})),
 				},
 			},
 			want: want{
@@ -601,17 +596,14 @@ func TestReconcile(t *testing.T) {
 		"ReconciliationPausedSuccessful": {
 			reason: `If a composite resource has the pause annotation with value "true", there should be no further requeue requests.`,
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
-						})),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
-							cr.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
-						})),
-					}),
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
+						cr.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+					})),
 				},
 			},
 			want: want{
@@ -621,14 +613,11 @@ func TestReconcile(t *testing.T) {
 		"ReconciliationPausedError": {
 			reason: `If a composite resource has the pause annotation with value "true" and the status update due to reconciliation being paused fails, error should be reported causing an exponentially backed-off requeue.`,
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
-						})),
-						MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
-					}),
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
+					})),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
 				},
 			},
 			want: want{
@@ -638,20 +627,19 @@ func TestReconcile(t *testing.T) {
 		"ReconciliationResumes": {
 			reason: `If a composite resource has the pause annotation with some value other than "true" and the Synced=False/ReconcilePaused status condition, reconciliation should resume with requeueing.`,
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: ""})
+						cr.SetConditions(xpv1.ReconcilePaused())
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: ""})
+						cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
+						cr.SetConnectionDetailsLastPublishedTime(&now)
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: ""})
-							cr.SetConditions(xpv1.ReconcilePaused())
-						})),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: ""})
-							cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
-							cr.SetConnectionDetailsLastPublishedTime(&now)
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -684,20 +672,19 @@ func TestReconcile(t *testing.T) {
 		"ReconciliationResumesAfterAnnotationRemoval": {
 			reason: `If a composite resource has the pause annotation removed and the Synced=False/ReconcilePaused status condition, reconciliation should resume with requeueing.`,
 			args: args{
-				mgr: &fake.Manager{},
+				client: &test.MockClient{
+					MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
+						// no annotation atm
+						// (but reconciliations were already paused)
+						cr.SetConditions(xpv1.ReconcilePaused())
+					})),
+					MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
+						cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
+						cr.SetConnectionDetailsLastPublishedTime(&now)
+						cr.SetCompositionReference(&corev1.ObjectReference{})
+					})),
+				},
 				opts: []ReconcilerOption{
-					WithClient(&test.MockClient{
-						MockGet: WithComposite(t, NewComposite(func(cr resource.Composite) {
-							// no annotation atm
-							// (but reconciliations were already paused)
-							cr.SetConditions(xpv1.ReconcilePaused())
-						})),
-						MockStatusUpdate: WantComposite(t, NewComposite(func(cr resource.Composite) {
-							cr.SetConditions(xpv1.ReconcileSuccess(), xpv1.Available())
-							cr.SetConnectionDetailsLastPublishedTime(&now)
-							cr.SetCompositionReference(&corev1.ObjectReference{})
-						})),
-					}),
 					WithCompositeFinalizer(resource.NewNopFinalizer()),
 					WithCompositionSelector(CompositionSelectorFn(func(_ context.Context, cr resource.Composite) error {
 						cr.SetCompositionReference(&corev1.ObjectReference{})
@@ -731,7 +718,7 @@ func TestReconcile(t *testing.T) {
 
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
-			r := NewReconciler(tc.args.mgr, tc.args.of, tc.args.opts...)
+			r := NewReconciler(tc.args.client, tc.args.of, tc.args.opts...)
 			got, err := r.Reconcile(context.Background(), reconcile.Request{})
 
 			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
@@ -843,204 +830,3 @@ func TestFilterToXRPatches(t *testing.T) {
 		})
 	}
 }
-
-func TestEnqueueForCompositionRevisionFunc(t *testing.T) {
-	type args struct {
-		of    schema.GroupVersionKind
-		list  func(_ context.Context, list client.ObjectList, opts ...client.ListOption) error
-		event runtimeevent.CreateEvent
-	}
-	type want struct {
-		added []interface{}
-	}
-
-	dog := schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Dog"}
-	dogList := dog.GroupVersion().WithKind("DogList")
-
-	tests := []struct {
-		name string
-		args args
-		want want
-	}{
-		{
-			name: "empty",
-			args: args{
-				of: dog,
-				list: func(_ context.Context, list client.ObjectList, opts ...client.ListOption) error {
-					// test parameters only here, not in the later tests for brevity.
-					u, ok := list.(*kunstructured.UnstructuredList)
-					if !ok {
-						t.Errorf("list was not an UnstructuredList")
-					} else if got := u.GroupVersionKind(); got != dogList {
-						t.Errorf("list was not a DogList, got: %s", got)
-					}
-					if len(opts) != 0 {
-						t.Errorf("unexpected list options: %#v", opts)
-					}
-					return nil
-				},
-			},
-		},
-		{
-			name: "automatic management policy",
-			args: args{
-				of: dog,
-				list: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-					var obj1 composite.Unstructured
-					obj1.SetNamespace("ns")
-					obj1.SetName("obj1")
-					policy := xpv1.UpdateAutomatic
-					obj1.SetCompositionUpdatePolicy(&policy)
-					obj1.SetCompositionReference(&corev1.ObjectReference{Name: "dachshund"})
-
-					list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{obj1.Unstructured}
-
-					return nil
-				},
-				event: runtimeevent.CreateEvent{
-					Object: &v1.CompositionRevision{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "dachshund-sadfa8",
-							Labels: map[string]string{
-								v1.LabelCompositionName: "dachshund",
-							},
-						},
-					},
-				},
-			},
-			want: want{
-				added: []interface{}{reconcile.Request{NamespacedName: types.NamespacedName{
-					Namespace: "ns",
-					Name:      "obj1",
-				}}},
-			},
-		},
-		{
-			name: "manual management policy",
-			args: args{
-				of: dog,
-				list: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-					var obj1 composite.Unstructured
-					obj1.SetNamespace("ns")
-					obj1.SetName("obj1")
-					policy := xpv1.UpdateManual
-					obj1.SetCompositionUpdatePolicy(&policy)
-					obj1.SetCompositionReference(&corev1.ObjectReference{Name: "dachshund"})
-
-					list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{obj1.Unstructured}
-
-					return nil
-				},
-				event: runtimeevent.CreateEvent{
-					Object: &v1.CompositionRevision{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "dachshund-sadfa8",
-							Labels: map[string]string{
-								v1.LabelCompositionName: "dachshund",
-							},
-						},
-					},
-				},
-			},
-			want: want{},
-		},
-		{
-			name: "other composition",
-			args: args{
-				of: dog,
-				list: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-					var obj1 composite.Unstructured
-					obj1.SetNamespace("ns")
-					obj1.SetName("obj1")
-					policy := xpv1.UpdateAutomatic
-					obj1.SetCompositionUpdatePolicy(&policy)
-					obj1.SetCompositionReference(&corev1.ObjectReference{Name: "bernese"})
-
-					list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{obj1.Unstructured}
-
-					return nil
-				},
-				event: runtimeevent.CreateEvent{
-					Object: &v1.CompositionRevision{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "dachshund-sadfa8",
-							Labels: map[string]string{
-								v1.LabelCompositionName: "dachshund",
-							},
-						},
-					},
-				},
-			},
-			want: want{},
-		},
-		{
-			name: "multiple",
-			args: args{
-				of: dog,
-				list: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-					var obj1 composite.Unstructured
-					obj1.SetNamespace("ns")
-					obj1.SetName("obj1")
-					automatic := xpv1.UpdateAutomatic
-					obj1.SetCompositionUpdatePolicy(&automatic)
-					obj1.SetCompositionReference(&corev1.ObjectReference{Name: "dachshund"})
-
-					obj2 := obj1.DeepCopy()
-					obj2.SetName("obj2")
-
-					obj3 := obj1.DeepCopy()
-					obj3.SetName("obj3")
-					obj3.SetCompositionReference(&corev1.ObjectReference{Name: "bernese"})
-
-					obj4 := obj1.DeepCopy()
-					obj4.SetName("obj4")
-					manual := xpv1.UpdateManual
-					obj4.SetCompositionUpdatePolicy(&manual)
-
-					list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{
-						obj1.Unstructured,
-						obj2.Unstructured,
-						obj3.Unstructured,
-					}
-
-					return nil
-				},
-				event: runtimeevent.CreateEvent{
-					Object: &v1.CompositionRevision{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "dachshund-sadfa8",
-							Labels: map[string]string{
-								v1.LabelCompositionName: "dachshund",
-							},
-						},
-					},
-				},
-			},
-			want: want{
-				added: []interface{}{
-					reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "ns", Name: "obj1"}},
-					reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "ns", Name: "obj2"}},
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			fn := EnqueueForCompositionRevisionFunc(resource.CompositeKind(tt.args.of), tt.args.list, logging.NewNopLogger())
-			q := rateLimitingQueueMock{}
-			fn(context.TODO(), tt.args.event, &q)
-			if got := q.added; !reflect.DeepEqual(got, tt.want.added) {
-				t.Errorf("EnqueueForCompositionRevisionFunc(...)(ctx, event, queue) = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-type rateLimitingQueueMock struct {
-	workqueue.RateLimitingInterface
-	added []interface{}
-}
-
-func (f *rateLimitingQueueMock) Add(item interface{}) {
-	f.added = append(f.added, item)
-}
diff --git a/internal/controller/apiextensions/composite/watch/watch_test.go b/internal/controller/apiextensions/composite/watch/watch_test.go
new file mode 100644
index 000000000..af72b5129
--- /dev/null
+++ b/internal/controller/apiextensions/composite/watch/watch_test.go
@@ -0,0 +1,244 @@
+/*
+Copyright 2024 The Crossplane 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 watch
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/internal/engine"
+)
+
+var _ ControllerEngine = &MockEngine{}
+
+type MockEngine struct {
+	MockGetWatches  func(name string) ([]engine.WatchID, error)
+	MockStopWatches func(ctx context.Context, name string, ws ...engine.WatchID) (int, error)
+	MockGetClient   func() client.Client
+}
+
+func (m *MockEngine) GetWatches(name string) ([]engine.WatchID, error) {
+	return m.MockGetWatches(name)
+}
+
+func (m *MockEngine) StopWatches(ctx context.Context, name string, ws ...engine.WatchID) (int, error) {
+	return m.MockStopWatches(ctx, name, ws...)
+}
+
+func (m *MockEngine) GetClient() client.Client {
+	return m.MockGetClient()
+}
+
+func TestGarbageCollectWatchesNow(t *testing.T) {
+	errBoom := errors.New("boom")
+
+	type params struct {
+		name string
+		of   resource.CompositeKind
+		ce   ControllerEngine
+		o    []GarbageCollectorOption
+	}
+	type args struct {
+		ctx context.Context
+	}
+	type want struct {
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"ListXRsError": {
+			reason: "The method should return an error if it can't list XRs.",
+			params: params{
+				ce: &MockEngine{
+					MockGetClient: func() client.Client {
+						return &test.MockClient{
+							MockList: test.NewMockListFn(errBoom),
+						}
+					},
+				},
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"GetWatchesError": {
+			reason: "The method should return an error if it can't get watches.",
+			params: params{
+				ce: &MockEngine{
+					MockGetClient: func() client.Client {
+						return &test.MockClient{
+							MockList: test.NewMockListFn(nil),
+						}
+					},
+					MockGetWatches: func(_ string) ([]engine.WatchID, error) {
+						return nil, errBoom
+					},
+				},
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"StopWatchesError": {
+			reason: "The method should return an error if it can't stop watches.",
+			params: params{
+				ce: &MockEngine{
+					MockGetClient: func() client.Client {
+						return &test.MockClient{
+							MockList: test.NewMockListFn(nil),
+						}
+					},
+					MockGetWatches: func(_ string) ([]engine.WatchID, error) {
+						w := []engine.WatchID{
+							{
+								Type: engine.WatchTypeComposedResource,
+								GVK:  schema.GroupVersionKind{},
+							},
+						}
+						return w, nil
+					},
+					MockStopWatches: func(_ context.Context, _ string, _ ...engine.WatchID) (int, error) {
+						return 0, errBoom
+					},
+				},
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"NothingToStop": {
+			reason: "StopWatches shouldn't be called if there's no watches to stop.",
+			params: params{
+				ce: &MockEngine{
+					MockGetClient: func() client.Client {
+						return &test.MockClient{
+							MockList: test.NewMockListFn(nil),
+						}
+					},
+					MockGetWatches: func(_ string) ([]engine.WatchID, error) {
+						return nil, nil
+					},
+					// StopWatches would panic if called, since it's not mocked.
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"UneededWatchesStopped": {
+			reason: "StopWatches shouldn't be called if there's no watches to stop.",
+			params: params{
+				ce: &MockEngine{
+					MockGetClient: func() client.Client {
+						return &test.MockClient{
+							MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error {
+								xr := composite.New()
+								xr.SetResourceReferences([]corev1.ObjectReference{
+									{
+										APIVersion: "example.org/v1",
+										Kind:       "StillComposed",
+										// Name doesn't matter.
+									},
+								})
+
+								obj.(*unstructured.UnstructuredList).Items = []unstructured.Unstructured{xr.Unstructured}
+
+								return nil
+							}),
+						}
+					},
+					MockGetWatches: func(_ string) ([]engine.WatchID, error) {
+						w := []engine.WatchID{
+							// We want to keep this one.
+							{
+								Type: engine.WatchTypeComposedResource,
+								GVK: schema.GroupVersionKind{
+									Group:   "example.org",
+									Version: "v1",
+									Kind:    "StillComposed",
+								},
+							},
+							// We want to GC this one.
+							{
+								Type: engine.WatchTypeComposedResource,
+								GVK: schema.GroupVersionKind{
+									Group:   "example.org",
+									Version: "v1",
+									Kind:    "GarbageCollectMe",
+								},
+							},
+						}
+						return w, nil
+					},
+					MockStopWatches: func(_ context.Context, _ string, ws ...engine.WatchID) (int, error) {
+						want := []engine.WatchID{
+							{
+								Type: engine.WatchTypeComposedResource,
+								GVK: schema.GroupVersionKind{
+									Group:   "example.org",
+									Version: "v1",
+									Kind:    "GarbageCollectMe",
+								},
+							},
+						}
+
+						if diff := cmp.Diff(want, ws); diff != "" {
+							t.Errorf("\nMockStopWatches(...) -want, +got:\n%s", diff)
+						}
+
+						return 0, nil
+					},
+				},
+				o: []GarbageCollectorOption{
+					WithLogger(logging.NewNopLogger()),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			gc := NewGarbageCollector(tc.params.name, tc.params.of, tc.params.ce, tc.params.o...)
+			err := gc.GarbageCollectWatchesNow(tc.args.ctx)
+
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ngc.GarbageCollectWatchesNow(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/controller/apiextensions/definition/handlers_test.go b/internal/controller/apiextensions/definition/handlers_test.go
new file mode 100644
index 000000000..9992c0931
--- /dev/null
+++ b/internal/controller/apiextensions/definition/handlers_test.go
@@ -0,0 +1,237 @@
+package definition
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/util/workqueue"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	kevent "sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+)
+
+func TestEnqueueForCompositionRevisionFunc(t *testing.T) {
+	type args struct {
+		of     schema.GroupVersionKind
+		reader client.Reader
+		event  kevent.CreateEvent
+	}
+	type want struct {
+		added []interface{}
+	}
+
+	dog := schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Dog"}
+	dogList := dog.GroupVersion().WithKind("DogList")
+
+	tests := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"NoXRs": {
+			reason: "If there are no XRs of the specified type, no reconciles should be enqueued.",
+			args: args{
+				of: dog,
+				reader: &test.MockClient{
+					MockList: func(_ context.Context, list client.ObjectList, opts ...client.ListOption) error {
+						// test parameters only here, not in the later tests for brevity.
+						u, ok := list.(*kunstructured.UnstructuredList)
+						if !ok {
+							t.Errorf("list was not an UnstructuredList")
+						} else if got := u.GroupVersionKind(); got != dogList {
+							t.Errorf("list was not a DogList, got: %s", got)
+						}
+						if len(opts) != 0 {
+							t.Errorf("unexpected list options: %#v", opts)
+						}
+						return nil
+					},
+				},
+			},
+		},
+		"AutomaticManagementPolicy": {
+			reason: "A reconcile should be enqueued for XRs with an automatic revision update policy.",
+			args: args{
+				of: dog,
+				reader: &test.MockClient{
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						var obj1 composite.Unstructured
+						obj1.SetNamespace("ns")
+						obj1.SetName("obj1")
+						policy := xpv1.UpdateAutomatic
+						obj1.SetCompositionUpdatePolicy(&policy)
+						obj1.SetCompositionReference(&corev1.ObjectReference{Name: "dachshund"})
+
+						list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{obj1.Unstructured}
+
+						return nil
+					},
+				},
+				event: kevent.CreateEvent{
+					Object: &v1.CompositionRevision{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "dachshund-sadfa8",
+							Labels: map[string]string{
+								v1.LabelCompositionName: "dachshund",
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				added: []interface{}{reconcile.Request{NamespacedName: types.NamespacedName{
+					Namespace: "ns",
+					Name:      "obj1",
+				}}},
+			},
+		},
+		"ManualManagementPolicy": {
+			reason: "A reconcile shouldn't be enqueued for XRs with a manual revision update policy.",
+			args: args{
+				of: dog,
+				reader: &test.MockClient{
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						var obj1 composite.Unstructured
+						obj1.SetNamespace("ns")
+						obj1.SetName("obj1")
+						policy := xpv1.UpdateManual
+						obj1.SetCompositionUpdatePolicy(&policy)
+						obj1.SetCompositionReference(&corev1.ObjectReference{Name: "dachshund"})
+
+						list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{obj1.Unstructured}
+
+						return nil
+					},
+				},
+				event: kevent.CreateEvent{
+					Object: &v1.CompositionRevision{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "dachshund-sadfa8",
+							Labels: map[string]string{
+								v1.LabelCompositionName: "dachshund",
+							},
+						},
+					},
+				},
+			},
+			want: want{},
+		},
+		"OtherComposition": {
+			reason: "A reconcile shouldn't be enqueued for an XR that references a different Composition",
+			args: args{
+				of: dog,
+				reader: &test.MockClient{
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						var obj1 composite.Unstructured
+						obj1.SetNamespace("ns")
+						obj1.SetName("obj1")
+						policy := xpv1.UpdateAutomatic
+						obj1.SetCompositionUpdatePolicy(&policy)
+						obj1.SetCompositionReference(&corev1.ObjectReference{Name: "bernese"})
+
+						list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{obj1.Unstructured}
+
+						return nil
+					},
+				},
+				event: kevent.CreateEvent{
+					Object: &v1.CompositionRevision{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "dachshund-sadfa8",
+							Labels: map[string]string{
+								v1.LabelCompositionName: "dachshund",
+							},
+						},
+					},
+				},
+			},
+			want: want{},
+		},
+		"Multiple": {
+			reason: "Reconciles should be enqueued only for the XRs that reference the relevant Composition, and have an automatic composition revision update policy.",
+			args: args{
+				of: dog,
+				reader: &test.MockClient{
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						var obj1 composite.Unstructured
+						obj1.SetNamespace("ns")
+						obj1.SetName("obj1")
+						automatic := xpv1.UpdateAutomatic
+						obj1.SetCompositionUpdatePolicy(&automatic)
+						obj1.SetCompositionReference(&corev1.ObjectReference{Name: "dachshund"})
+
+						obj2 := obj1.DeepCopy()
+						obj2.SetName("obj2")
+
+						obj3 := obj1.DeepCopy()
+						obj3.SetName("obj3")
+						obj3.SetCompositionReference(&corev1.ObjectReference{Name: "bernese"})
+
+						obj4 := obj1.DeepCopy()
+						obj4.SetName("obj4")
+						manual := xpv1.UpdateManual
+						obj4.SetCompositionUpdatePolicy(&manual)
+
+						list.(*kunstructured.UnstructuredList).Items = []kunstructured.Unstructured{
+							obj1.Unstructured,
+							obj2.Unstructured,
+							obj3.Unstructured,
+						}
+
+						return nil
+					},
+				},
+				event: kevent.CreateEvent{
+					Object: &v1.CompositionRevision{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "dachshund-sadfa8",
+							Labels: map[string]string{
+								v1.LabelCompositionName: "dachshund",
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				added: []interface{}{
+					reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "ns", Name: "obj1"}},
+					reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "ns", Name: "obj2"}},
+				},
+			},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			fns := EnqueueForCompositionRevision(resource.CompositeKind(tc.args.of), tc.args.reader, logging.NewNopLogger())
+			q := rateLimitingQueueMock{}
+			fns.Create(context.TODO(), tc.args.event, &q)
+
+			if diff := cmp.Diff(tc.want.added, q.added); diff != "" {
+				t.Errorf("\n%s\nfns.Create(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+type rateLimitingQueueMock struct {
+	workqueue.RateLimitingInterface
+	added []interface{}
+}
+
+func (f *rateLimitingQueueMock) Add(item interface{}) {
+	f.added = append(f.added, item)
+}
diff --git a/internal/controller/apiextensions/definition/indexes_test.go b/internal/controller/apiextensions/definition/indexes_test.go
index 3e725ddc0..40fc1315e 100644
--- a/internal/controller/apiextensions/definition/indexes_test.go
+++ b/internal/controller/apiextensions/definition/indexes_test.go
@@ -25,50 +25,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-func TestIndexCompositeResourceRefGVKs(t *testing.T) {
-	type args struct {
-		object client.Object
-	}
-	tests := map[string]struct {
-		args args
-		want []string
-	}{
-		"Nil":             {args: args{object: nil}, want: nil},
-		"NotUnstructured": {args: args{object: &corev1.Pod{}}, want: nil},
-		"NoRefs": {args: args{object: &unstructured.Unstructured{
-			Object: map[string]interface{}{
-				"spec": map[string]interface{}{},
-			},
-		}}, want: []string{}},
-		"References": {args: args{object: &unstructured.Unstructured{
-			Object: map[string]interface{}{
-				"spec": map[string]interface{}{
-					"resourceRefs": []interface{}{
-						map[string]interface{}{
-							"apiVersion": "nop.crossplane.io/v1alpha1",
-							"kind":       "NopResource",
-							"name":       "mr",
-						},
-						map[string]interface{}{
-							"apiVersion": "nop.example.org/v1alpha1",
-							"kind":       "NopResource",
-							"name":       "xr",
-						},
-					},
-				},
-			},
-		}}, want: []string{"nop.crossplane.io/v1alpha1, Kind=NopResource", "nop.example.org/v1alpha1, Kind=NopResource"}},
-	}
-	for name, tc := range tests {
-		t.Run(name, func(t *testing.T) {
-			got := IndexCompositeResourceRefGVKs(tc.args.object)
-			if diff := cmp.Diff(tc.want, got); diff != "" {
-				t.Errorf("\n%s\nIndexCompositeResourceRefGVKs(...): -want, +got:\n%s", name, diff)
-			}
-		})
-	}
-}
-
 func TestIndexCompositeResourcesRefs(t *testing.T) {
 	type args struct {
 		object client.Object
diff --git a/internal/controller/apiextensions/definition/reconciler_test.go b/internal/controller/apiextensions/definition/reconciler_test.go
index 78a5ef4f7..c92ca9b62 100644
--- a/internal/controller/apiextensions/definition/reconciler_test.go
+++ b/internal/controller/apiextensions/definition/reconciler_test.go
@@ -20,62 +20,70 @@ import (
 	"context"
 	"testing"
 
-	"github.com/go-logr/logr"
 	"github.com/google/go-cmp/cmp"
 	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/rest"
-	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	ctrlconfig "sigs.k8s.io/controller-runtime/pkg/config"
-	kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
-	"github.com/crossplane/crossplane-runtime/pkg/controller"
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
-	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
-	"github.com/crossplane/crossplane/internal/features"
+	"github.com/crossplane/crossplane/internal/engine"
+)
+
+var (
+	_ ControllerEngine = &MockEngine{}
+	_ ControllerEngine = &NopEngine{}
 )
 
 type MockEngine struct {
-	ControllerEngine
-	MockIsRunning func(name string) bool
-	MockCreate    func(name string, o kcontroller.Options, w ...controller.Watch) (controller.NamedController, error)
-	MockStart     func(name string, o kcontroller.Options, w ...controller.Watch) error
-	MockStop      func(name string)
-	MockErr       func(name string) error
+	MockStart           func(name string, o ...engine.ControllerOption) error
+	MockStop            func(ctx context.Context, name string) error
+	MockIsRunning       func(name string) bool
+	MockGetWatches      func(name string) ([]engine.WatchID, error)
+	MockStartWatches    func(name string, ws ...engine.Watch) error
+	MockStopWatches     func(ctx context.Context, name string, ws ...engine.WatchID) (int, error)
+	MockGetClient       func() client.Client
+	MockGetFieldIndexer func() client.FieldIndexer
 }
 
 func (m *MockEngine) IsRunning(name string) bool {
 	return m.MockIsRunning(name)
 }
 
-func (m *MockEngine) Create(name string, o kcontroller.Options, w ...controller.Watch) (controller.NamedController, error) {
-	return m.MockCreate(name, o, w...)
+func (m *MockEngine) Start(name string, o ...engine.ControllerOption) error {
+	return m.MockStart(name, o...)
+}
+
+func (m *MockEngine) Stop(ctx context.Context, name string) error {
+	return m.MockStop(ctx, name)
+}
+
+func (m *MockEngine) GetWatches(name string) ([]engine.WatchID, error) {
+	return m.MockGetWatches(name)
+}
+
+func (m *MockEngine) StartWatches(name string, ws ...engine.Watch) error {
+	return m.MockStartWatches(name, ws...)
 }
 
-func (m *MockEngine) Start(name string, o kcontroller.Options, w ...controller.Watch) error {
-	return m.MockStart(name, o, w...)
+func (m *MockEngine) StopWatches(ctx context.Context, name string, ws ...engine.WatchID) (int, error) {
+	return m.MockStopWatches(ctx, name, ws...)
 }
 
-func (m *MockEngine) Stop(name string) {
-	m.MockStop(name)
+func (m *MockEngine) GetClient() client.Client {
+	return m.MockGetClient()
 }
 
-func (m *MockEngine) Err(name string) error {
-	return m.MockErr(name)
+func (m *MockEngine) GetFieldIndexer() client.FieldIndexer {
+	return m.MockGetFieldIndexer()
 }
 
 func TestReconcile(t *testing.T) {
@@ -85,7 +93,7 @@ func TestReconcile(t *testing.T) {
 	ctrlr := true
 
 	type args struct {
-		mgr  manager.Manager
+		ca   resource.ClientApplicator
 		opts []ReconcilerOption
 	}
 	type want struct {
@@ -101,13 +109,10 @@ func TestReconcile(t *testing.T) {
 		"CompositeResourceDefinitionNotFound": {
 			reason: "We should not return an error if the CompositeResourceDefinition was not found.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
-						},
-					}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
+					},
 				},
 			},
 			want: want{
@@ -117,13 +122,10 @@ func TestReconcile(t *testing.T) {
 		"GetCompositeResourceDefinitionError": {
 			reason: "We should return any other error encountered while getting a CompositeResourceDefinition.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(errBoom),
-						},
-					}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(errBoom),
+					},
 				},
 			},
 			want: want{
@@ -133,13 +135,12 @@ func TestReconcile(t *testing.T) {
 		"RenderCustomResourceDefinitionError": {
 			reason: "We should return any error we encounter rendering a CRD.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return nil, errBoom
 					})),
@@ -152,18 +153,17 @@ func TestReconcile(t *testing.T) {
 		"SetTerminatingConditionError": {
 			reason: "We should return any error we encounter while setting the terminating status condition.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							d := o.(*v1.CompositeResourceDefinition)
+							d.SetDeletionTimestamp(&now)
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								d := o.(*v1.CompositeResourceDefinition)
-								d.SetDeletionTimestamp(&now)
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -176,24 +176,23 @@ func TestReconcile(t *testing.T) {
 		"GetCustomResourceDefinitionError": {
 			reason: "We should return any error we encounter getting a CRD.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								return errBoom
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									return errBoom
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -203,24 +202,54 @@ func TestReconcile(t *testing.T) {
 				err: errors.Wrap(errBoom, errGetCRD),
 			},
 		},
-		"RemoveFinalizerError": {
-			reason: "We should return any error we encounter while removing a finalizer.",
+		"CustomResourceDefinitionNotFoundStopControllerError": {
+			reason: "We should return any error we encounter while stopping our controller (just in case) when the CRD doesn't exist.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							if v, ok := o.(*v1.CompositeResourceDefinition); ok {
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								if v, ok := o.(*v1.CompositeResourceDefinition); ok {
-									d := v1.CompositeResourceDefinition{}
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{}, nil
+					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error {
+							return errBoom
 						},
 					}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errStopController),
+			},
+		},
+		"RemoveFinalizerError": {
+			reason: "We should return any error we encounter while removing a finalizer.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							if v, ok := o.(*v1.CompositeResourceDefinition); ok {
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -236,21 +265,20 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulDelete": {
 			reason: "We should not requeue when deleted if we successfully cleaned up our CRD and removed our finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							if v, ok := o.(*v1.CompositeResourceDefinition); ok {
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								if v, ok := o.(*v1.CompositeResourceDefinition); ok {
-									d := v1.CompositeResourceDefinition{}
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -266,29 +294,28 @@ func TestReconcile(t *testing.T) {
 		"DeleteAllCustomResourcesError": {
 			reason: "We should return any error we encounter while deleting all defined resources.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf:  test.NewMockDeleteAllOfFn(errBoom),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockDeleteAllOf:  test.NewMockDeleteAllOfFn(errBoom),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -301,30 +328,29 @@ func TestReconcile(t *testing.T) {
 		"ListCustomResourcesError": {
 			reason: "We should return any error we encounter while listing all defined resources.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf:  test.NewMockDeleteAllOfFn(nil),
+						MockList:         test.NewMockListFn(errBoom),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockDeleteAllOf:  test.NewMockDeleteAllOfFn(nil),
-							MockList:         test.NewMockListFn(errBoom),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -337,36 +363,35 @@ func TestReconcile(t *testing.T) {
 		"WaitForDeleteAllOf": {
 			reason: "We should record the pending deletion of defined resources.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf: test.NewMockDeleteAllOfFn(nil),
+						MockList: test.NewMockListFn(nil, func(o client.ObjectList) error {
+							v := o.(*unstructured.UnstructuredList)
+							*v = unstructured.UnstructuredList{
+								Items: []unstructured.Unstructured{{}, {}},
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockDeleteAllOf: test.NewMockDeleteAllOfFn(nil),
-							MockList: test.NewMockListFn(nil, func(o client.ObjectList) error {
-								v := o.(*unstructured.UnstructuredList)
-								*v = unstructured.UnstructuredList{
-									Items: []unstructured.Unstructured{{}, {}},
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -376,34 +401,73 @@ func TestReconcile(t *testing.T) {
 				r: reconcile.Result{Requeue: true},
 			},
 		},
-		"DeleteCustomResourceDefinitionError": {
-			reason: "We should return any error we encounter while deleting the CRD we created.",
+		"StopControllerError": {
+			reason: "We should return any error we encounter while stopping our controller.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf:  test.NewMockDeleteAllOfFn(nil),
+						MockList:         test.NewMockListFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockDeleteAllOf:  test.NewMockDeleteAllOfFn(nil),
-							MockList:         test.NewMockListFn(nil),
-							MockDelete:       test.NewMockDeleteFn(errBoom),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{}, nil
+					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error {
+							return errBoom
 						},
 					}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errStopController),
+			},
+		},
+		"DeleteCustomResourceDefinitionError": {
+			reason: "We should return any error we encounter while deleting the CRD we created.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf:  test.NewMockDeleteAllOfFn(nil),
+						MockList:         test.NewMockListFn(nil),
+						MockDelete:       test.NewMockDeleteFn(errBoom),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -416,42 +480,41 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulCleanup": {
 			reason: "We should requeue to remove our finalizer once we've cleaned up our defined resources and CRD.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockDeleteAllOf: test.NewMockDeleteAllOfFn(nil),
-							MockList:        test.NewMockListFn(nil),
-							MockDelete:      test.NewMockDeleteFn(nil),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(got client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.SetUID(owner)
-								want.SetDeletionTimestamp(&now)
-								want.Status.SetConditions(v1.TerminatingComposite())
-
-								if diff := cmp.Diff(want, got); diff != "" {
-									t.Errorf("MockStatusUpdate: -want, +got:\n%s\n", diff)
-								}
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf: test.NewMockDeleteAllOfFn(nil),
+						MockList:        test.NewMockListFn(nil),
+						MockDelete:      test.NewMockDeleteFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(got client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.SetUID(owner)
+							want.SetDeletionTimestamp(&now)
+							want.Status.SetConditions(v1.TerminatingComposite())
+
+							if diff := cmp.Diff(want, got); diff != "" {
+								t.Errorf("MockStatusUpdate: -want, +got:\n%s\n", diff)
+							}
 
-								return nil
-							}),
-						},
-					}),
+							return nil
+						}),
+					},
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -464,13 +527,12 @@ func TestReconcile(t *testing.T) {
 		"AddFinalizerError": {
 			reason: "We should return any error we encounter while adding a finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -486,16 +548,15 @@ func TestReconcile(t *testing.T) {
 		"ApplyCustomResourceDefinitionError": {
 			reason: "We should return any error we encounter while applying our CRD.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
-							return errBoom
-						}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return errBoom
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -511,16 +572,15 @@ func TestReconcile(t *testing.T) {
 		"CustomResourceDefinitionIsNotEstablished": {
 			reason: "We should requeue if we're waiting for a newly created CRD to become established.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
-							return nil
-						}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -533,32 +593,84 @@ func TestReconcile(t *testing.T) {
 				r: reconcile.Result{Requeue: true},
 			},
 		},
-		"CreateControllerError": {
-			reason: "We should return any error we encounter while starting our controller.",
+		"VersionChangedStopControllerError": {
+			reason: "We should return any error we encounter while stopping our controller because the XRD's referencable version changed.",
 			args: args{
-				mgr: &mockManager{
-					GetCacheFn: func() cache.Cache {
-						return &mockCache{
-							ListFn: func(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error { return nil },
-						}
-					},
-					GetClientFn: func() client.Client {
-						return &test.MockClient{MockList: test.NewMockListFn(nil)}
-					},
-					GetSchemeFn: runtime.NewScheme,
-					GetRESTMapperFn: func() meta.RESTMapper {
-						return meta.NewDefaultRESTMapper([]schema.GroupVersion{v1.SchemeGroupVersion})
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							xrd := &v1.CompositeResourceDefinition{
+								Spec: v1.CompositeResourceDefinitionSpec{
+									Group: "example.org",
+									Names: extv1.CustomResourceDefinitionNames{
+										Kind: "XR",
+									},
+									Versions: []v1.CompositeResourceDefinitionVersion{
+										{
+											Name:          "v2",
+											Referenceable: true,
+										},
+										{
+											Name: "v1",
+										},
+									},
+								},
+								Status: v1.CompositeResourceDefinitionStatus{
+									Controllers: v1.CompositeResourceDefinitionControllerStatus{
+										CompositeResourceTypeRef: v1.TypeReference{
+											APIVersion: "example.org/v1",
+											Kind:       "XR",
+										},
+									},
+								},
+							}
+
+							*obj.(*v1.CompositeResourceDefinition) = *xrd
+							return nil
+						}),
 					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
+					}),
 				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{
+							Status: extv1.CustomResourceDefinitionStatus{
+								Conditions: []extv1.CustomResourceDefinitionCondition{
+									{Type: extv1.Established, Status: extv1.ConditionTrue},
+								},
+							},
+						}, nil
+					})),
+					WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
+						return nil
+					}}),
+					WithControllerEngine(&MockEngine{
+						MockIsRunning: func(_ string) bool { return false },
+						MockStop: func(_ context.Context, _ string) error {
+							return errBoom
 						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
-							return nil
-						}),
 					}),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: errors.Wrap(errBoom, errStopController),
+			},
+		},
+		"StartControllerError": {
+			reason: "We should return any error we encounter while starting our controller.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
+					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -573,10 +685,10 @@ func TestReconcile(t *testing.T) {
 					}}),
 					WithControllerEngine(&MockEngine{
 						MockIsRunning: func(_ string) bool { return false },
-						MockErr:       func(_ string) error { return nil },
-						MockCreate: func(_ string, _ kcontroller.Options, _ ...controller.Watch) (controller.NamedController, error) {
-							return nil, errBoom
+						MockStart: func(_ string, _ ...engine.ControllerOption) error {
+							return errBoom
 						},
+						MockGetClient: func() client.Client { return test.NewMockClient() },
 					}),
 				},
 			},
@@ -585,41 +697,68 @@ func TestReconcile(t *testing.T) {
 				err: errors.Wrap(errBoom, errStartController),
 			},
 		},
-		"SuccessfulStart": {
-			reason: "We should return without requeueing if we successfully ensured our CRD exists and controller is started.",
+		"StartWatchesError": {
+			reason: "We should return any error we encounter while starting watches.",
 			args: args{
-				mgr: &mockManager{
-					GetCacheFn: func() cache.Cache {
-						return &mockCache{
-							ListFn: func(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error { return nil },
-						}
-					},
-					GetClientFn: func() client.Client {
-						return &test.MockClient{MockList: test.NewMockListFn(nil)}
-					},
-					GetSchemeFn: runtime.NewScheme,
-					GetRESTMapperFn: func() meta.RESTMapper {
-						return meta.NewDefaultRESTMapper([]schema.GroupVersion{v1.SchemeGroupVersion})
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
 					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
+					}),
 				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.Status.SetConditions(v1.WatchingComposite())
-
-								if diff := cmp.Diff(want, o); diff != "" {
-									t.Errorf("-want, +got:\n%s", diff)
-								}
-								return nil
-							}),
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{
+							Status: extv1.CustomResourceDefinitionStatus{
+								Conditions: []extv1.CustomResourceDefinitionCondition{
+									{Type: extv1.Established, Status: extv1.ConditionTrue},
+								},
+							},
+						}, nil
+					})),
+					WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
+						return nil
+					}}),
+					WithControllerEngine(&MockEngine{
+						MockIsRunning: func(_ string) bool { return false },
+						MockStart: func(_ string, _ ...engine.ControllerOption) error {
+							return nil
+						},
+						MockStartWatches: func(_ string, _ ...engine.Watch) error {
+							return errBoom
 						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						MockGetClient: func() client.Client { return test.NewMockClient() },
+					}),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: errors.Wrap(errBoom, errStartWatches),
+			},
+		},
+		"SuccessfulStart": {
+			reason: "We should return without requeueing if we successfully ensured our CRD exists and controller is started.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.Status.SetConditions(v1.WatchingComposite())
+
+							if diff := cmp.Diff(want, o); diff != "" {
+								t.Errorf("-want, +got:\n%s", diff)
+							}
 							return nil
 						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -633,23 +772,10 @@ func TestReconcile(t *testing.T) {
 						return nil
 					}}),
 					WithControllerEngine(&MockEngine{
-						MockIsRunning: func(_ string) bool { return false },
-						MockErr:       func(_ string) error { return errBoom }, // This error should only be logged.
-						MockCreate: func(_ string, _ kcontroller.Options, _ ...controller.Watch) (controller.NamedController, error) {
-							return mockNamedController{
-								MockStart: func(_ context.Context) error { return nil },
-								MockGetCache: func() cache.Cache {
-									return &mockCache{
-										IndexFieldFn: func(_ context.Context, _ client.Object, _ string, _ client.IndexerFunc) error {
-											return nil
-										},
-										WaitForCacheSyncFn: func(_ context.Context) bool {
-											return true
-										},
-									}
-								},
-							}, nil
-						},
+						MockIsRunning:    func(_ string) bool { return false },
+						MockStart:        func(_ string, _ ...engine.ControllerOption) error { return nil },
+						MockStartWatches: func(_ string, _ ...engine.Watch) error { return nil },
+						MockGetClient:    func() client.Client { return test.NewMockClient() },
 					}),
 				},
 			},
@@ -660,51 +786,37 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulUpdateControllerVersion": {
 			reason: "We should return without requeueing if we successfully ensured our CRD exists, the old controller stopped, and the new one started.",
 			args: args{
-				mgr: &mockManager{
-					GetCacheFn: func() cache.Cache {
-						return &mockCache{
-							ListFn: func(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error { return nil },
-						}
-					},
-					GetClientFn: func() client.Client {
-						return &test.MockClient{MockList: test.NewMockListFn(nil)}
-					},
-					GetSchemeFn: runtime.NewScheme,
-					GetRESTMapperFn: func() meta.RESTMapper {
-						return meta.NewDefaultRESTMapper([]schema.GroupVersion{v1.SchemeGroupVersion})
-					},
-				},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								d := obj.(*v1.CompositeResourceDefinition)
-								d.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
-									{Name: "old", Referenceable: false},
-									{Name: "new", Referenceable: true},
-								}
-								d.Status.Controllers.CompositeResourceTypeRef = v1.TypeReference{APIVersion: "old"}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
-									{Name: "old", Referenceable: false},
-									{Name: "new", Referenceable: true},
-								}
-								want.Status.Controllers.CompositeResourceTypeRef = v1.TypeReference{APIVersion: "new"}
-								want.Status.SetConditions(v1.WatchingComposite())
-
-								if diff := cmp.Diff(want, o); diff != "" {
-									t.Errorf("-want, +got:\n%s", diff)
-								}
-								return nil
-							}),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							d := obj.(*v1.CompositeResourceDefinition)
+							d.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
+								{Name: "old", Referenceable: false},
+								{Name: "new", Referenceable: true},
+							}
+							d.Status.Controllers.CompositeResourceTypeRef = v1.TypeReference{APIVersion: "old"}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
+								{Name: "old", Referenceable: false},
+								{Name: "new", Referenceable: true},
+							}
+							want.Status.Controllers.CompositeResourceTypeRef = v1.TypeReference{APIVersion: "new"}
+							want.Status.SetConditions(v1.WatchingComposite())
+
+							if diff := cmp.Diff(want, o); diff != "" {
+								t.Errorf("-want, +got:\n%s", diff)
+							}
 							return nil
 						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -718,23 +830,11 @@ func TestReconcile(t *testing.T) {
 						return nil
 					}}),
 					WithControllerEngine(&MockEngine{
-						MockErr: func(_ string) error { return nil },
-						MockCreate: func(_ string, _ kcontroller.Options, _ ...controller.Watch) (controller.NamedController, error) {
-							return mockNamedController{
-								MockStart: func(_ context.Context) error { return nil },
-								MockGetCache: func() cache.Cache {
-									return &mockCache{
-										IndexFieldFn: func(_ context.Context, _ client.Object, _ string, _ client.IndexerFunc) error {
-											return nil
-										},
-										WaitForCacheSyncFn: func(_ context.Context) bool {
-											return true
-										},
-									}
-								},
-							}, nil
-						},
-						MockStop: func(_ string) {},
+						MockStart:        func(_ string, _ ...engine.ControllerOption) error { return nil },
+						MockStop:         func(_ context.Context, _ string) error { return nil },
+						MockIsRunning:    func(_ string) bool { return false },
+						MockStartWatches: func(_ string, _ ...engine.Watch) error { return nil },
+						MockGetClient:    func() client.Client { return test.NewMockClient() },
 					}),
 				},
 			},
@@ -745,38 +845,24 @@ func TestReconcile(t *testing.T) {
 		"NotRestartingWithoutVersionChange": {
 			reason: "We should return without requeueing if we successfully ensured our CRD exists and controller is started.",
 			args: args{
-				mgr: &mockManager{
-					GetCacheFn: func() cache.Cache {
-						return &mockCache{
-							ListFn: func(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error { return nil },
-						}
-					},
-					GetClientFn: func() client.Client {
-						return &test.MockClient{MockList: test.NewMockListFn(nil)}
-					},
-					GetSchemeFn: runtime.NewScheme,
-					GetRESTMapperFn: func() meta.RESTMapper {
-						return meta.NewDefaultRESTMapper([]schema.GroupVersion{v1.SchemeGroupVersion})
-					},
-				},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.Status.SetConditions(v1.WatchingComposite())
-
-								if diff := cmp.Diff(want, o); diff != "" {
-									t.Errorf("-want, +got:\n%s", diff)
-								}
-								return nil
-							}),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.Status.SetConditions(v1.WatchingComposite())
+
+							if diff := cmp.Diff(want, o); diff != "" {
+								t.Errorf("-want, +got:\n%s", diff)
+							}
 							return nil
 						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -791,10 +877,9 @@ func TestReconcile(t *testing.T) {
 					}}),
 					WithControllerEngine(&MockEngine{
 						MockIsRunning: func(_ string) bool { return true },
-						MockErr:       func(_ string) error { return errBoom }, // This error should only be logged.
-						MockCreate: func(_ string, _ kcontroller.Options, _ ...controller.Watch) (controller.NamedController, error) {
-							t.Errorf("MockCreate should not be called")
-							return nil, nil
+						MockStart: func(_ string, _ ...engine.ControllerOption) error {
+							t.Errorf("MockStart should not be called")
+							return nil
 						},
 					}),
 				},
@@ -805,105 +890,17 @@ func TestReconcile(t *testing.T) {
 		},
 	}
 
-	// Run every test with and without the realtime compositions feature.
-	rtc := apiextensionscontroller.Options{Options: controller.DefaultOptions()}
-	rtc.Features.Enable(features.EnableAlphaRealtimeCompositions)
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			r := NewReconciler(tc.args.ca, tc.args.opts...)
+			got, err := r.Reconcile(context.Background(), reconcile.Request{})
 
-	type mode struct {
-		name  string
-		extra []ReconcilerOption
-	}
-	for _, m := range []mode{
-		{name: "Default"},
-		{name: string(features.EnableAlphaRealtimeCompositions), extra: []ReconcilerOption{WithOptions(rtc)}},
-	} {
-		t.Run(m.name, func(t *testing.T) {
-			for name, tc := range cases {
-				t.Run(name, func(t *testing.T) {
-					r := NewReconciler(tc.args.mgr, append(tc.args.opts, m.extra...)...)
-					got, err := r.Reconcile(context.Background(), reconcile.Request{})
-
-					if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
-						t.Errorf("\n%s\nr.Reconcile(...): -want error, +got error:\n%s", tc.reason, diff)
-					}
-					if diff := cmp.Diff(tc.want.r, got, test.EquateErrors()); diff != "" {
-						t.Errorf("\n%s\nr.Reconcile(...): -want, +got:\n%s", tc.reason, diff)
-					}
-				})
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.r, got, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want, +got:\n%s", tc.reason, diff)
 			}
 		})
 	}
 }
-
-type mockNamedController struct {
-	MockStart    func(_ context.Context) error
-	MockGetCache func() cache.Cache
-}
-
-func (m mockNamedController) Start(ctx context.Context) error {
-	return m.MockStart(ctx)
-}
-
-func (m mockNamedController) GetCache() cache.Cache {
-	return m.MockGetCache()
-}
-
-type mockManager struct {
-	manager.Manager
-
-	GetCacheFn             func() cache.Cache
-	GetClientFn            func() client.Client
-	GetSchemeFn            func() *runtime.Scheme
-	GetRESTMapperFn        func() meta.RESTMapper
-	GetConfigFn            func() *rest.Config
-	GetLoggerFn            func() logr.Logger
-	GetControllerOptionsFn func() ctrlconfig.Controller
-}
-
-func (m *mockManager) GetCache() cache.Cache {
-	return m.GetCacheFn()
-}
-
-func (m *mockManager) GetClient() client.Client {
-	return m.GetClientFn()
-}
-
-func (m *mockManager) GetScheme() *runtime.Scheme {
-	return m.GetSchemeFn()
-}
-
-func (m *mockManager) GetRESTMapper() meta.RESTMapper {
-	return m.GetRESTMapperFn()
-}
-
-func (m *mockManager) GetConfig() *rest.Config {
-	return m.GetConfigFn()
-}
-
-func (m *mockManager) GetLogger() logr.Logger {
-	return m.GetLoggerFn()
-}
-
-func (m *mockManager) GetControllerOptions() ctrlconfig.Controller {
-	return m.GetControllerOptionsFn()
-}
-
-type mockCache struct {
-	cache.Cache
-
-	ListFn             func(_ context.Context, list client.ObjectList, opts ...client.ListOption) error
-	IndexFieldFn       func(_ context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error
-	WaitForCacheSyncFn func(_ context.Context) bool
-}
-
-func (m *mockCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
-	return m.ListFn(ctx, list, opts...)
-}
-
-func (m *mockCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
-	return m.IndexFieldFn(ctx, obj, field, extractValue)
-}
-
-func (m *mockCache) WaitForCacheSync(ctx context.Context) bool {
-	return m.WaitForCacheSyncFn(ctx)
-}
diff --git a/internal/controller/apiextensions/offered/reconciler_test.go b/internal/controller/apiextensions/offered/reconciler_test.go
index 59166a641..366117324 100644
--- a/internal/controller/apiextensions/offered/reconciler_test.go
+++ b/internal/controller/apiextensions/offered/reconciler_test.go
@@ -29,38 +29,49 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
-	"github.com/crossplane/crossplane-runtime/pkg/controller"
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 	"github.com/crossplane/crossplane-runtime/pkg/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+	"github.com/crossplane/crossplane/internal/engine"
 )
 
 type MockEngine struct {
-	ControllerEngine
-	MockStart func(name string, o kcontroller.Options, w ...controller.Watch) error
-	MockStop  func(name string)
-	MockErr   func(name string) error
+	MockStart        func(name string, o ...engine.ControllerOption) error
+	MockStop         func(ctx context.Context, name string) error
+	MockIsRunning    func(name string) bool
+	MockStartWatches func(name string, ws ...engine.Watch) error
+	MockGetClient    func() client.Client
 }
 
-func (m *MockEngine) Start(name string, o kcontroller.Options, w ...controller.Watch) error {
-	return m.MockStart(name, o, w...)
+var (
+	_ ControllerEngine = &MockEngine{}
+	_ ControllerEngine = &NopEngine{}
+)
+
+func (m *MockEngine) Start(name string, o ...engine.ControllerOption) error {
+	return m.MockStart(name, o...)
 }
 
-func (m *MockEngine) Stop(name string) {
-	m.MockStop(name)
+func (m *MockEngine) Stop(ctx context.Context, name string) error {
+	return m.MockStop(ctx, name)
 }
 
-func (m *MockEngine) Err(name string) error {
-	return m.MockErr(name)
+func (m *MockEngine) IsRunning(name string) bool {
+	return m.MockIsRunning(name)
+}
+
+func (m *MockEngine) StartWatches(name string, ws ...engine.Watch) error {
+	return m.MockStartWatches(name, ws...)
+}
+
+func (m *MockEngine) GetClient() client.Client {
+	return m.MockGetClient()
 }
 
 func TestReconcile(t *testing.T) {
@@ -71,7 +82,7 @@ func TestReconcile(t *testing.T) {
 	ctrlr := true
 
 	type args struct {
-		mgr  manager.Manager
+		ca   resource.ClientApplicator
 		opts []ReconcilerOption
 	}
 	type want struct {
@@ -87,13 +98,10 @@ func TestReconcile(t *testing.T) {
 		"CompositeResourceDefinitionNotFound": {
 			reason: "We should not return an error if the CompositeResourceDefinition was not found.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
-						},
-					}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
+					},
 				},
 			},
 			want: want{
@@ -103,13 +111,10 @@ func TestReconcile(t *testing.T) {
 		"GetCompositeResourceDefinitionError": {
 			reason: "We should return any other error encountered while getting a CompositeResourceDefinition.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(errBoom),
-						},
-					}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(errBoom),
+					},
 				},
 			},
 			want: want{
@@ -119,13 +124,12 @@ func TestReconcile(t *testing.T) {
 		"RenderCompositeResourceDefinitionError": {
 			reason: "We should return any error we encounter while rendering a CRD.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return nil, errBoom
 					})),
@@ -138,18 +142,17 @@ func TestReconcile(t *testing.T) {
 		"SetTerminatingConditionError": {
 			reason: "We should return any error we encounter while setting the terminating status condition.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							d := o.(*v1.CompositeResourceDefinition)
+							d.SetDeletionTimestamp(&now)
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								d := o.(*v1.CompositeResourceDefinition)
-								d.SetDeletionTimestamp(&now)
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -162,24 +165,23 @@ func TestReconcile(t *testing.T) {
 		"GetCustomResourceDefinitionError": {
 			reason: "We should return any error we encounter while getting a CRD.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								return errBoom
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									return errBoom
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -189,27 +191,60 @@ func TestReconcile(t *testing.T) {
 				err: errors.Wrap(errBoom, errGetCRD),
 			},
 		},
-		"RemoveFinalizerError": {
-			reason: "We should return any error we encounter while removing a finalizer.",
+		"CustomResourceDefinitionNotFoundStopControllerError": {
+			reason: "We should return any error we encounter while stopping our controller (just in case) when the CRD doesn't exist.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							if v, ok := o.(*v1.CompositeResourceDefinition); ok {
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								if v, ok := o.(*v1.CompositeResourceDefinition); ok {
-									d := v1.CompositeResourceDefinition{}
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{}, nil
+					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error {
+							return errBoom
 						},
 					}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errStopController),
+			},
+		},
+		"RemoveFinalizerError": {
+			reason: "We should return any error we encounter while removing a finalizer.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							if v, ok := o.(*v1.CompositeResourceDefinition); ok {
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error { return nil },
+					}),
 					WithFinalizer(resource.FinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error {
 						return errBoom
 					}}),
@@ -222,24 +257,26 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulDelete": {
 			reason: "We should not requeue when deleted if we successfully cleaned up our CRD and removed our finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							if v, ok := o.(*v1.CompositeResourceDefinition); ok {
+								d := v1.CompositeResourceDefinition{}
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								if v, ok := o.(*v1.CompositeResourceDefinition); ok {
-									d := v1.CompositeResourceDefinition{}
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error { return nil },
+					}),
 					WithFinalizer(resource.FinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error {
 						return nil
 					}}),
@@ -252,29 +289,28 @@ func TestReconcile(t *testing.T) {
 		"ListCustomResourcesError": {
 			reason: "We should return any error we encounter while listing all defined resources.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockList:         test.NewMockListFn(errBoom),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockList:         test.NewMockListFn(errBoom),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -287,36 +323,35 @@ func TestReconcile(t *testing.T) {
 		"DeleteCustomResourcesError": {
 			reason: "We should return any error we encounter while deleting defined resources.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockList: test.NewMockListFn(nil, func(o client.ObjectList) error {
+							v := o.(*unstructured.UnstructuredList)
+							*v = unstructured.UnstructuredList{
+								Items: []unstructured.Unstructured{{}, {}},
+							}
+							return nil
+						}),
+						MockDelete:       test.NewMockDeleteFn(errBoom),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockList: test.NewMockListFn(nil, func(o client.ObjectList) error {
-								v := o.(*unstructured.UnstructuredList)
-								*v = unstructured.UnstructuredList{
-									Items: []unstructured.Unstructured{{}, {}},
-								}
-								return nil
-							}),
-							MockDelete:       test.NewMockDeleteFn(errBoom),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -329,36 +364,35 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulDeleteCustomResources": {
 			reason: "We should requeue to ensure our defined resources are gone before we remove our CRD.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockList: test.NewMockListFn(nil, func(o client.ObjectList) error {
+							v := o.(*unstructured.UnstructuredList)
+							*v = unstructured.UnstructuredList{
+								Items: []unstructured.Unstructured{{}, {}},
+							}
+							return nil
+						}),
+						MockDelete:       test.NewMockDeleteFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockList: test.NewMockListFn(nil, func(o client.ObjectList) error {
-								v := o.(*unstructured.UnstructuredList)
-								*v = unstructured.UnstructuredList{
-									Items: []unstructured.Unstructured{{}, {}},
-								}
-								return nil
-							}),
-							MockDelete:       test.NewMockDeleteFn(nil),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -368,38 +402,80 @@ func TestReconcile(t *testing.T) {
 				r: reconcile.Result{Requeue: true},
 			},
 		},
-		"DeleteCustomResourceDefinitionError": {
-			reason: "We should return any error we encounter while deleting the CRD we created.",
+		"StopControllerError": {
+			reason: "We should return any error we encounter while stopping our controller.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockDeleteAllOf:  test.NewMockDeleteAllOfFn(nil),
+						MockList:         test.NewMockListFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{
-										Spec: v1.CompositeResourceDefinitionSpec{},
-									}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockList:         test.NewMockListFn(nil),
-							MockDelete:       test.NewMockDeleteFn(errBoom),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{}, nil
+					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error {
+							return errBoom
 						},
 					}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errStopController),
+			},
+		},
+		"DeleteCustomResourceDefinitionError": {
+			reason: "We should return any error we encounter while deleting the CRD we created.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{
+									Spec: v1.CompositeResourceDefinitionSpec{},
+								}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockList:         test.NewMockListFn(nil),
+						MockDelete:       test.NewMockDeleteFn(errBoom),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error { return nil },
+					}),
 				},
 			},
 			want: want{
@@ -409,44 +485,46 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulCleanup": {
 			reason: "We should requeue to remove our finalizer once we've cleaned up our defined resources and CRD.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
-								switch v := o.(type) {
-								case *v1.CompositeResourceDefinition:
-									d := v1.CompositeResourceDefinition{}
-									d.SetUID(owner)
-									d.SetDeletionTimestamp(&now)
-									*v = d
-								case *extv1.CustomResourceDefinition:
-									crd := extv1.CustomResourceDefinition{}
-									crd.SetCreationTimestamp(now)
-									crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
-									*v = crd
-								}
-								return nil
-							}),
-							MockList:   test.NewMockListFn(nil),
-							MockDelete: test.NewMockDeleteFn(nil),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(got client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.SetUID(owner)
-								want.SetDeletionTimestamp(&now)
-								want.Status.SetConditions(v1.TerminatingClaim())
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(o client.Object) error {
+							switch v := o.(type) {
+							case *v1.CompositeResourceDefinition:
+								d := v1.CompositeResourceDefinition{}
+								d.SetUID(owner)
+								d.SetDeletionTimestamp(&now)
+								*v = d
+							case *extv1.CustomResourceDefinition:
+								crd := extv1.CustomResourceDefinition{}
+								crd.SetCreationTimestamp(now)
+								crd.SetOwnerReferences([]metav1.OwnerReference{{UID: owner, Controller: &ctrlr}})
+								*v = crd
+							}
+							return nil
+						}),
+						MockList:   test.NewMockListFn(nil),
+						MockDelete: test.NewMockDeleteFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(got client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.SetUID(owner)
+							want.SetDeletionTimestamp(&now)
+							want.Status.SetConditions(v1.TerminatingClaim())
 
-								if diff := cmp.Diff(want, got); diff != "" {
-									t.Errorf("MockStatusUpdate: -want, +got:\n%s\n", diff)
-								}
+							if diff := cmp.Diff(want, got); diff != "" {
+								t.Errorf("MockStatusUpdate: -want, +got:\n%s\n", diff)
+							}
 
-								return nil
-							}),
-						},
-					}),
+							return nil
+						}),
+					},
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
+					WithControllerEngine(&MockEngine{
+						MockStop: func(_ context.Context, _ string) error { return nil },
+					}),
 				},
 			},
 			want: want{
@@ -456,13 +534,12 @@ func TestReconcile(t *testing.T) {
 		"AddFinalizerError": {
 			reason: "We should return any error we encounter while adding a finalizer.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-					}),
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -478,16 +555,15 @@ func TestReconcile(t *testing.T) {
 		"ApplyCRDError": {
 			reason: "We should return any error we encounter while applying our CRD.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
-							return errBoom
-						}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return errBoom
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{}, nil
 					})),
@@ -503,41 +579,105 @@ func TestReconcile(t *testing.T) {
 		"CustomResourceDefinitionIsNotEstablished": {
 			reason: "We should requeue if we're waiting for a newly created CRD to become established.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
+					}),
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{}, nil
+					})),
+					WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
+						return nil
+					}}),
+				},
+			},
+			want: want{
+				r: reconcile.Result{Requeue: true},
+			},
+		},
+		"VersionChangedStopControllerError": {
+			reason: "We should return any error we encounter while stopping our controller because the XRD's referencable version changed.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							xrd := &v1.CompositeResourceDefinition{
+								Spec: v1.CompositeResourceDefinitionSpec{
+									Group: "example.org",
+									ClaimNames: &extv1.CustomResourceDefinitionNames{
+										Kind: "Claim",
+									},
+									Versions: []v1.CompositeResourceDefinitionVersion{
+										{
+											Name:          "v2",
+											Referenceable: true,
+										},
+										{
+											Name: "v1",
+										},
+									},
+								},
+								Status: v1.CompositeResourceDefinitionStatus{
+									Controllers: v1.CompositeResourceDefinitionControllerStatus{
+										CompositeResourceClaimTypeRef: v1.TypeReference{
+											APIVersion: "example.org/v1",
+											Kind:       "Claim",
+										},
+									},
+								},
+							}
+
+							*obj.(*v1.CompositeResourceDefinition) = *xrd
 							return nil
 						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
-						return &extv1.CustomResourceDefinition{}, nil
+						return &extv1.CustomResourceDefinition{
+							Status: extv1.CustomResourceDefinitionStatus{
+								Conditions: []extv1.CustomResourceDefinitionCondition{
+									{Type: extv1.Established, Status: extv1.ConditionTrue},
+								},
+							},
+						}, nil
 					})),
 					WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
 						return nil
 					}}),
+					WithControllerEngine(&MockEngine{
+						MockIsRunning: func(_ string) bool { return false },
+						MockStop: func(_ context.Context, _ string) error {
+							return errBoom
+						},
+					}),
 				},
 			},
 			want: want{
-				r: reconcile.Result{Requeue: true},
+				r:   reconcile.Result{},
+				err: errors.Wrap(errBoom, errStopController),
 			},
 		},
 		"StartControllerError": {
 			reason: "We should return any error we encounter while starting our controller.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
-							return nil
-						}),
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -551,8 +691,9 @@ func TestReconcile(t *testing.T) {
 						return nil
 					}}),
 					WithControllerEngine(&MockEngine{
-						MockErr:   func(_ string) error { return nil },
-						MockStart: func(_ string, _ kcontroller.Options, _ ...controller.Watch) error { return errBoom },
+						MockIsRunning: func(_ string) bool { return false },
+						MockStart:     func(_ string, _ ...engine.ControllerOption) error { return errBoom },
+						MockGetClient: func() client.Client { return test.NewMockClient() },
 					}),
 				},
 			},
@@ -560,28 +701,68 @@ func TestReconcile(t *testing.T) {
 				err: errors.Wrap(errBoom, errStartController),
 			},
 		},
+		"StartWatchesError": {
+			reason: "We should return any error we encounter while starting watches.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
+					}),
+				},
+				opts: []ReconcilerOption{
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{
+							Status: extv1.CustomResourceDefinitionStatus{
+								Conditions: []extv1.CustomResourceDefinitionCondition{
+									{Type: extv1.Established, Status: extv1.ConditionTrue},
+								},
+							},
+						}, nil
+					})),
+					WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
+						return nil
+					}}),
+					WithControllerEngine(&MockEngine{
+						MockIsRunning: func(_ string) bool { return false },
+						MockStart: func(_ string, _ ...engine.ControllerOption) error {
+							return nil
+						},
+						MockStartWatches: func(_ string, _ ...engine.Watch) error {
+							return errBoom
+						},
+						MockGetClient: func() client.Client { return test.NewMockClient() },
+					}),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: errors.Wrap(errBoom, errStartWatches),
+			},
+		},
 		"SuccessfulStart": {
 			reason: "We should not requeue if we successfully ensured our CRD exists and controller is started.",
 			args: args{
-				mgr: &fake.Manager{},
-				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.Status.SetConditions(v1.WatchingClaim())
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.Status.SetConditions(v1.WatchingClaim())
 
-								if diff := cmp.Diff(want, o); diff != "" {
-									t.Errorf("-want, +got:\n%s", diff)
-								}
-								return nil
-							}),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+							if diff := cmp.Diff(want, o); diff != "" {
+								t.Errorf("-want, +got:\n%s", diff)
+							}
 							return nil
 						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -595,8 +776,10 @@ func TestReconcile(t *testing.T) {
 						return nil
 					}}),
 					WithControllerEngine(&MockEngine{
-						MockErr:   func(_ string) error { return errBoom }, // This error should only be logged.
-						MockStart: func(_ string, _ kcontroller.Options, _ ...controller.Watch) error { return nil },
+						MockIsRunning:    func(_ string) bool { return false },
+						MockStart:        func(_ string, _ ...engine.ControllerOption) error { return nil },
+						MockStartWatches: func(_ string, _ ...engine.Watch) error { return nil },
+						MockGetClient:    func() client.Client { return test.NewMockClient() },
 					},
 					),
 				},
@@ -608,40 +791,85 @@ func TestReconcile(t *testing.T) {
 		"SuccessfulUpdateControllerVersion": {
 			reason: "We should not requeue if we successfully ensured our CRD exists, the old controller stopped, and the new one started.",
 			args: args{
-				mgr: &fake.Manager{},
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							d := obj.(*v1.CompositeResourceDefinition)
+							d.Spec.ClaimNames = &extv1.CustomResourceDefinitionNames{}
+							d.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
+								{Name: "old", Referenceable: false},
+								{Name: "new", Referenceable: true},
+							}
+							d.Status.Controllers.CompositeResourceClaimTypeRef = v1.TypeReference{APIVersion: "old"}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.Spec.ClaimNames = &extv1.CustomResourceDefinitionNames{}
+							want.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
+								{Name: "old", Referenceable: false},
+								{Name: "new", Referenceable: true},
+							}
+							want.Status.Controllers.CompositeResourceClaimTypeRef = v1.TypeReference{APIVersion: "new"}
+							want.Status.SetConditions(v1.WatchingClaim())
+
+							if diff := cmp.Diff(want, o); diff != "" {
+								t.Errorf("-want, +got:\n%s", diff)
+							}
+							return nil
+						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
+					}),
+				},
 				opts: []ReconcilerOption{
-					WithClientApplicator(resource.ClientApplicator{
-						Client: &test.MockClient{
-							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								d := obj.(*v1.CompositeResourceDefinition)
-								d.Spec.ClaimNames = &extv1.CustomResourceDefinitionNames{}
-								d.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
-									{Name: "old", Referenceable: false},
-									{Name: "new", Referenceable: true},
-								}
-								d.Status.Controllers.CompositeResourceClaimTypeRef = v1.TypeReference{APIVersion: "old"}
-								return nil
-							}),
-							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
-								want := &v1.CompositeResourceDefinition{}
-								want.Spec.ClaimNames = &extv1.CustomResourceDefinitionNames{}
-								want.Spec.Versions = []v1.CompositeResourceDefinitionVersion{
-									{Name: "old", Referenceable: false},
-									{Name: "new", Referenceable: true},
-								}
-								want.Status.Controllers.CompositeResourceClaimTypeRef = v1.TypeReference{APIVersion: "new"}
-								want.Status.SetConditions(v1.WatchingClaim())
+					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+						return &extv1.CustomResourceDefinition{
+							Status: extv1.CustomResourceDefinitionStatus{
+								Conditions: []extv1.CustomResourceDefinitionCondition{
+									{Type: extv1.Established, Status: extv1.ConditionTrue},
+								},
+							},
+						}, nil
+					})),
+					WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error {
+						return nil
+					}}),
+					WithControllerEngine(&MockEngine{
+						MockStart:        func(_ string, _ ...engine.ControllerOption) error { return nil },
+						MockStop:         func(_ context.Context, _ string) error { return nil },
+						MockIsRunning:    func(_ string) bool { return false },
+						MockStartWatches: func(_ string, _ ...engine.Watch) error { return nil },
+						MockGetClient:    func() client.Client { return test.NewMockClient() },
+					}),
+				},
+			},
+			want: want{
+				r: reconcile.Result{Requeue: false},
+			},
+		},
+		"NotRestartingWithoutVersionChange": {
+			reason: "We should return without requeueing if we successfully ensured our CRD exists and controller is started.",
+			args: args{
+				ca: resource.ClientApplicator{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error {
+							want := &v1.CompositeResourceDefinition{}
+							want.Status.SetConditions(v1.WatchingClaim())
 
-								if diff := cmp.Diff(want, o); diff != "" {
-									t.Errorf("-want, +got:\n%s", diff)
-								}
-								return nil
-							}),
-						},
-						Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+							if diff := cmp.Diff(want, o); diff != "" {
+								t.Errorf("-want, +got:\n%s", diff)
+							}
 							return nil
 						}),
+					},
+					Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
+						return nil
 					}),
+				},
+				opts: []ReconcilerOption{
 					WithCRDRenderer(CRDRenderFn(func(_ *v1.CompositeResourceDefinition) (*extv1.CustomResourceDefinition, error) {
 						return &extv1.CustomResourceDefinition{
 							Status: extv1.CustomResourceDefinitionStatus{
@@ -655,9 +883,11 @@ func TestReconcile(t *testing.T) {
 						return nil
 					}}),
 					WithControllerEngine(&MockEngine{
-						MockErr:   func(_ string) error { return nil },
-						MockStart: func(_ string, _ kcontroller.Options, _ ...controller.Watch) error { return nil },
-						MockStop:  func(_ string) {},
+						MockIsRunning: func(_ string) bool { return true },
+						MockStart: func(_ string, _ ...engine.ControllerOption) error {
+							t.Errorf("MockStart should not be called")
+							return nil
+						},
 					}),
 				},
 			},
@@ -669,7 +899,7 @@ func TestReconcile(t *testing.T) {
 
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
-			r := NewReconciler(tc.args.mgr, append(tc.args.opts, WithLogger(testLog))...)
+			r := NewReconciler(tc.args.ca, append(tc.args.opts, WithLogger(testLog))...)
 			got, err := r.Reconcile(context.Background(), reconcile.Request{})
 
 			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
diff --git a/internal/dag/dag_test.go b/internal/dag/dag_test.go
index 787226166..8da0a2fb0 100644
--- a/internal/dag/dag_test.go
+++ b/internal/dag/dag_test.go
@@ -37,7 +37,6 @@ func (s *simpleNode) Neighbors() []Node {
 	nodes := make([]Node, len(s.neighbors))
 	i := 0
 	for _, r := range s.neighbors {
-		r := r // Pin range variable so we can take its address.
 		nodes[i] = &r
 		i++
 	}
@@ -58,7 +57,6 @@ func (s *simpleNode) AddNeighbors(nodes ...Node) error {
 func toNodes(n []simpleNode) []Node {
 	nodes := make([]Node, len(n))
 	for i, r := range n {
-		r := r // Pin range variable so we can take its address.
 		nodes[i] = &r
 	}
 	return nodes
diff --git a/internal/dag/fuzz_test.go b/internal/dag/fuzz_test.go
index 0d492c5a3..a05fbc21e 100644
--- a/internal/dag/fuzz_test.go
+++ b/internal/dag/fuzz_test.go
@@ -31,7 +31,6 @@ type SimpleFuzzNode struct {
 func toNodesFuzz(n []SimpleFuzzNode) []Node {
 	nodes := make([]Node, len(n))
 	for i, r := range n {
-		r := r // Pin range variable so we can take its address.
 		nodes[i] = &r
 	}
 	return nodes
@@ -59,7 +58,6 @@ func (s *SimpleFuzzNode) Neighbors() []Node {
 	nodes := make([]Node, len(s.NeighborsField))
 	i := 0
 	for _, r := range s.NeighborsField {
-		r := r // Pin range variable so we can take its address.
 		nodes[i] = &r
 		i++
 	}
diff --git a/internal/engine/cache_test.go b/internal/engine/cache_test.go
new file mode 100644
index 000000000..8fac54e60
--- /dev/null
+++ b/internal/engine/cache_test.go
@@ -0,0 +1,166 @@
+/*
+Copyright 2024 The Crossplane 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 engine
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+var _ cache.Cache = &MockCache{}
+
+type MockCache struct {
+	cache.Cache
+
+	MockGet                func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error
+	MockList               func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error
+	MockGetInformer        func(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error)
+	MockGetInformerForKind func(ctx context.Context, gvk schema.GroupVersionKind, opts ...cache.InformerGetOption) (cache.Informer, error)
+	MockRemoveInformer     func(ctx context.Context, obj client.Object) error
+}
+
+func (m *MockCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
+	return m.MockGet(ctx, key, obj, opts...)
+}
+
+func (m *MockCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
+	return m.MockList(ctx, list, opts...)
+}
+
+func (m *MockCache) GetInformer(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error) {
+	return m.MockGetInformer(ctx, obj, opts...)
+}
+
+func (m *MockCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...cache.InformerGetOption) (cache.Informer, error) {
+	return m.MockGetInformerForKind(ctx, gvk, opts...)
+}
+
+func (m *MockCache) RemoveInformer(ctx context.Context, obj client.Object) error {
+	return m.MockRemoveInformer(ctx, obj)
+}
+
+func TestActiveInformers(t *testing.T) {
+	c := &MockCache{
+		MockGet: func(_ context.Context, _ client.ObjectKey, _ client.Object, _ ...client.GetOption) error {
+			return nil
+		},
+		MockList: func(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error {
+			return nil
+		},
+		MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+			return nil, nil
+		},
+		MockGetInformerForKind: func(_ context.Context, _ schema.GroupVersionKind, _ ...cache.InformerGetOption) (cache.Informer, error) {
+			return nil, nil
+		},
+		MockRemoveInformer: func(_ context.Context, _ client.Object) error { return nil },
+	}
+
+	itc := TrackInformers(c, runtime.NewScheme())
+
+	ctx := context.Background()
+
+	// We intentionally call methods twice to cover the code paths where we
+	// don't start tracking an informer because we already track it (and vice
+	// versa for remove).
+
+	// Get a GVK
+	get := &unstructured.Unstructured{}
+	get.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "test.crossplane.io",
+		Version: "v1",
+		Kind:    "Get",
+	})
+	_ = itc.Get(ctx, client.ObjectKeyFromObject(get), get)
+	_ = itc.Get(ctx, client.ObjectKeyFromObject(get), get)
+
+	// List a GVK
+	list := &unstructured.UnstructuredList{}
+	list.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "test.crossplane.io",
+		Version: "v1",
+		Kind:    "ListList", // It's a list list!
+	})
+	_ = itc.List(ctx, list)
+	_ = itc.List(ctx, list)
+
+	// Get an informer
+	getinf := &unstructured.Unstructured{}
+	getinf.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "test.crossplane.io",
+		Version: "v1",
+		Kind:    "GetInformer",
+	})
+	_, _ = itc.GetInformer(ctx, getinf)
+	_, _ = itc.GetInformer(ctx, getinf)
+
+	// Get an informer by GVK
+	getgvk := schema.GroupVersionKind{
+		Group:   "test.crossplane.io",
+		Version: "v1",
+		Kind:    "GetInformerForKind",
+	}
+	_, _ = itc.GetInformerForKind(ctx, getgvk)
+	_, _ = itc.GetInformerForKind(ctx, getgvk)
+
+	// Get a GVK, then remove its informer.
+	remove := &unstructured.Unstructured{}
+	remove.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "test.crossplane.io",
+		Version: "v1",
+		Kind:    "RemoveMe",
+	})
+	_ = itc.Get(ctx, client.ObjectKeyFromObject(remove), remove)
+	_ = itc.RemoveInformer(ctx, remove)
+	_ = itc.RemoveInformer(ctx, remove)
+
+	want := []schema.GroupVersionKind{
+		{
+			Group:   "test.crossplane.io",
+			Version: "v1",
+			Kind:    "Get",
+		},
+		{
+			Group:   "test.crossplane.io",
+			Version: "v1",
+			Kind:    "List",
+		},
+		{
+			Group:   "test.crossplane.io",
+			Version: "v1",
+			Kind:    "GetInformer",
+		},
+		{
+			Group:   "test.crossplane.io",
+			Version: "v1",
+			Kind:    "GetInformerForKind",
+		},
+	}
+
+	got := itc.ActiveInformers()
+	if diff := cmp.Diff(want, got, cmpopts.SortSlices(func(a, b schema.GroupVersionKind) bool { return a.String() > b.String() })); diff != "" {
+		t.Errorf("\nitc.ActiveInformers(...): -want, +got:\n%s", diff)
+	}
+}
diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go
new file mode 100644
index 000000000..62ca8b463
--- /dev/null
+++ b/internal/engine/engine_test.go
@@ -0,0 +1,897 @@
+/*
+Copyright 2020 The Crossplane 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 engine
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/source"
+)
+
+var _ TrackingInformers = &MockTrackingInformers{}
+
+type MockTrackingInformers struct {
+	cache.Informers
+
+	MockActiveInformers func() []schema.GroupVersionKind
+	MockGetInformer     func(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error)
+	MockRemoveInformer  func(ctx context.Context, obj client.Object) error
+}
+
+func (m *MockTrackingInformers) ActiveInformers() []schema.GroupVersionKind {
+	return m.MockActiveInformers()
+}
+
+func (m *MockTrackingInformers) GetInformer(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error) {
+	return m.MockGetInformer(ctx, obj, opts...)
+}
+
+func (m *MockTrackingInformers) RemoveInformer(ctx context.Context, obj client.Object) error {
+	return m.MockRemoveInformer(ctx, obj)
+}
+
+var _ manager.Manager = &MockManager{}
+
+type MockManager struct {
+	manager.Manager
+
+	MockElected   func() <-chan struct{}
+	MockGetScheme func() *runtime.Scheme
+}
+
+func (m *MockManager) Elected() <-chan struct{} {
+	return m.MockElected()
+}
+
+func (m *MockManager) GetScheme() *runtime.Scheme {
+	return m.MockGetScheme()
+}
+
+var _ WatchGarbageCollector = &MockWatchGarbageCollector{}
+
+type MockWatchGarbageCollector struct {
+	MockGarbageCollectWatches func(ctx context.Context, interval time.Duration)
+}
+
+func (m *MockWatchGarbageCollector) GarbageCollectWatches(ctx context.Context, interval time.Duration) {
+	m.MockGarbageCollectWatches(ctx, interval)
+}
+
+var _ kcontroller.Controller = &MockController{}
+
+type MockController struct {
+	kcontroller.Controller
+
+	MockStart func(ctx context.Context) error
+	MockWatch func(src source.Source) error
+}
+
+func (m *MockController) Start(ctx context.Context) error {
+	return m.MockStart(ctx)
+}
+
+func (m *MockController) Watch(src source.Source) error {
+	return m.MockWatch(src)
+}
+
+func TestStartController(t *testing.T) {
+	type params struct {
+		mgr  manager.Manager
+		infs TrackingInformers
+		c    client.Client
+		opts []ControllerEngineOption
+	}
+	type args struct {
+		name string
+		opts []ControllerOption
+	}
+	type want struct {
+		err error
+	}
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"NewControllerError": {
+			reason: "Start should return an error if it can't create a new controller",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+				},
+				infs: &MockTrackingInformers{},
+			},
+			args: args{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return nil, errors.New("boom")
+					}),
+				},
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"StartControllerError": {
+			reason: "Start won't return an error if it can't start the new controller in a goroutine.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+				},
+				infs: &MockTrackingInformers{},
+			},
+			args: args{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return &MockController{
+							MockStart: func(_ context.Context) error {
+								return errors.New("boom")
+							},
+						}, nil
+					}),
+				},
+			},
+			// TODO(negz): Test that the error was logged? We usually don't.
+			want: want{
+				err: nil,
+			},
+		},
+		"SuccessfulStart": {
+			reason: "It should be possible to successfully start a controller.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+				},
+				infs: &MockTrackingInformers{},
+			},
+			args: args{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return &MockController{
+							MockStart: func(ctx context.Context) error {
+								<-ctx.Done()
+								return nil
+							},
+						}, nil
+					}),
+					WithWatchGarbageCollector(&MockWatchGarbageCollector{
+						MockGarbageCollectWatches: func(ctx context.Context, _ time.Duration) {
+							<-ctx.Done()
+						},
+					}),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			e := New(tc.params.mgr, tc.params.infs, tc.params.c, tc.params.opts...)
+			err := e.Start(tc.args.name, tc.args.opts...)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Starting the controller second time should be a no-op.
+			err = e.Start(tc.args.name, tc.args.opts...)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Stop the controller. Will be a no-op if it never started.
+			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+			defer cancel()
+			err = e.Stop(ctx, tc.args.name)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Stop(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestIsRunning(t *testing.T) {
+	type params struct {
+		mgr  manager.Manager
+		infs TrackingInformers
+		c    client.Client
+		opts []ControllerEngineOption
+	}
+
+	// We need to control how we start the controller.
+	type argsStart struct {
+		name string
+		opts []ControllerOption
+	}
+	type args struct {
+		name string
+	}
+	type want struct {
+		running bool
+	}
+	cases := map[string]struct {
+		reason    string
+		params    params
+		argsStart argsStart
+		args      args
+		want      want
+	}{
+		"SuccessfulStart": {
+			reason: "IsRunning should return true if the controller successfully starts.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+				},
+				infs: &MockTrackingInformers{},
+			},
+			argsStart: argsStart{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return &MockController{
+							MockStart: func(ctx context.Context) error {
+								<-ctx.Done()
+								return nil
+							},
+						}, nil
+					}),
+				},
+			},
+			args: args{
+				name: "cool-controller",
+			},
+			want: want{
+				running: true,
+			},
+		},
+		"StartControllerError": {
+			reason: "IsRunning should return false if the controller didn't successfully start.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+				},
+				infs: &MockTrackingInformers{},
+			},
+			argsStart: argsStart{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return &MockController{
+							MockStart: func(_ context.Context) error {
+								return errors.New("boom")
+							},
+						}, nil
+					}),
+				},
+			},
+			args: args{
+				name: "cool-controller",
+			},
+			want: want{
+				running: false,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			e := New(tc.params.mgr, tc.params.infs, tc.params.c, tc.params.opts...)
+			_ = e.Start(tc.args.name, tc.argsStart.opts...)
+
+			// Give the start goroutine a little time to fail.
+			time.Sleep(1 * time.Second)
+
+			running := e.IsRunning(tc.args.name)
+			if diff := cmp.Diff(tc.want.running, running); diff != "" {
+				t.Errorf("\n%s\ne.IsRunning(...): -want, +got:\n%s", tc.reason, diff)
+			}
+
+			// Stop the controller. Will be a no-op if it never started.
+			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+			defer cancel()
+			_ = e.Stop(ctx, tc.args.name)
+
+			// IsRunning should always be false after the controller is stopped.
+			running = e.IsRunning(tc.args.name)
+			if diff := cmp.Diff(false, running); diff != "" {
+				t.Errorf("\n%s\ne.IsRunning(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestStopController(t *testing.T) {
+	type params struct {
+		mgr  manager.Manager
+		infs TrackingInformers
+		c    client.Client
+		opts []ControllerEngineOption
+	}
+	type args struct {
+		ctx  context.Context
+		name string
+	}
+	type want struct {
+		err error
+	}
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"SuccessfulStop": {
+			reason: "It should be possible to successfully stop a controller.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+					MockGetScheme: runtime.NewScheme,
+				},
+				infs: &MockTrackingInformers{
+					MockActiveInformers: func() []schema.GroupVersionKind {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx:  context.Background(),
+				name: "cool-controller",
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		// TODO(negz): Test handling watches that fail to stop? I'm not sure
+		// it's worth the amount of complexity making StoppableSource injectable
+		// would add. We could make Watch an interface with a GetSource.
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			e := New(tc.params.mgr, tc.params.infs, tc.params.c, tc.params.opts...)
+			err := e.Start(tc.args.name, WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+				return &MockController{
+					MockStart: func(ctx context.Context) error {
+						<-ctx.Done()
+						return nil
+					},
+					MockWatch: func(_ source.Source) error {
+						return nil
+					},
+				}, nil
+			}))
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Fatalf("\n%s\ne.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Add a watch for stop to stop.
+			u := &unstructured.Unstructured{}
+			u.SetAPIVersion("test.crossplane.io/v1")
+			u.SetKind("Composed")
+			err = e.StartWatches(tc.args.name, WatchFor(u, WatchTypeComposedResource, nil))
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.StartWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Stop the controller. Will be a no-op if it never started.
+			err = e.Stop(tc.args.ctx, tc.args.name)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Stop(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Stop should be a no-op when called on a stopped controller.
+			err = e.Stop(tc.args.ctx, tc.args.name)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Stop(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestStartWatches(t *testing.T) {
+	type params struct {
+		mgr  manager.Manager
+		infs TrackingInformers
+		c    client.Client
+		opts []ControllerEngineOption
+	}
+	// We need to control how we start the controller.
+	type argsStart struct {
+		name string
+		opts []ControllerOption
+	}
+	type args struct {
+		name string
+		ws   []Watch
+	}
+	type want struct {
+		err     error
+		watches []WatchID
+	}
+	cases := map[string]struct {
+		reason    string
+		params    params
+		argsStart argsStart
+		args      args
+		want      want
+	}{
+		"StartWatchError": {
+			reason: "StartWatches should return an error when a watch fails to start.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+					MockGetScheme: runtime.NewScheme,
+				},
+				infs: &MockTrackingInformers{
+					MockActiveInformers: func() []schema.GroupVersionKind {
+						return []schema.GroupVersionKind{
+							{
+								Group:   "test.crossplane.io",
+								Version: "v1",
+								Kind:    "Composed",
+							},
+						}
+					},
+				},
+			},
+			argsStart: argsStart{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return &MockController{
+							MockStart: func(ctx context.Context) error {
+								<-ctx.Done()
+								return nil
+							},
+							MockWatch: func(_ source.Source) error {
+								return errors.New("boom")
+							},
+						}, nil
+					}),
+				},
+			},
+			args: args{
+				name: "cool-controller",
+				ws: []Watch{
+					func() Watch {
+						u := &unstructured.Unstructured{}
+						u.SetAPIVersion("test.crossplane.io/v1")
+						u.SetKind("Composed")
+						return WatchFor(u, WatchTypeComposedResource, nil)
+					}(),
+				},
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"SuccessfulStartWatches": {
+			reason: "StartWatches shouldn't return an error when all watches start successfully.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+					MockGetScheme: runtime.NewScheme,
+				},
+				infs: &MockTrackingInformers{
+					MockActiveInformers: func() []schema.GroupVersionKind {
+						return []schema.GroupVersionKind{
+							{
+								Group:   "test.crossplane.io",
+								Version: "v1",
+								Kind:    "Resource",
+							},
+						}
+					},
+				},
+			},
+			argsStart: argsStart{
+				name: "cool-controller",
+				opts: []ControllerOption{
+					WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+						return &MockController{
+							MockStart: func(ctx context.Context) error {
+								<-ctx.Done()
+								return nil
+							},
+							MockWatch: func(_ source.Source) error {
+								return nil
+							},
+						}, nil
+					}),
+				},
+			},
+			args: args{
+				name: "cool-controller",
+				ws: []Watch{
+					func() Watch {
+						u := &unstructured.Unstructured{}
+						u.SetAPIVersion("test.crossplane.io/v1")
+						u.SetKind("Resource")
+						return WatchFor(u, WatchTypeComposedResource, nil)
+					}(),
+					// This should be deduplicated into the above watch.
+					func() Watch {
+						u := &unstructured.Unstructured{}
+						u.SetAPIVersion("test.crossplane.io/v1")
+						u.SetKind("Resource")
+						return WatchFor(u, WatchTypeComposedResource, nil)
+					}(),
+					// This shouldn't be deduplicated, because it's a different
+					// watch type.
+					func() Watch {
+						u := &unstructured.Unstructured{}
+						u.SetAPIVersion("test.crossplane.io/v1")
+						u.SetKind("Resource")
+						return WatchFor(u, WatchTypeCompositeResource, nil)
+					}(),
+				},
+			},
+			want: want{
+				err: nil,
+				watches: []WatchID{
+					{
+						Type: WatchTypeComposedResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+					{
+						Type: WatchTypeCompositeResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			e := New(tc.params.mgr, tc.params.infs, tc.params.c, tc.params.opts...)
+			err := e.Start(tc.argsStart.name, tc.argsStart.opts...)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Fatalf("\n%s\ne.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			err = e.StartWatches(tc.args.name, tc.args.ws...)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.StartWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Start the same watches again to exercise the code that ensures we
+			// only add each watch once.
+			err = e.StartWatches(tc.args.name, tc.args.ws...)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.StartWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			watches, err := e.GetWatches(tc.args.name)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.GetWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.watches, watches,
+				cmpopts.EquateEmpty(),
+				cmpopts.SortSlices(func(a, b WatchID) bool { return fmt.Sprintf("%s", a) > fmt.Sprintf("%s", b) }),
+			); diff != "" {
+				t.Errorf("\n%s\ne.StartWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Stop the controller. Will be a no-op if it never started.
+			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+			defer cancel()
+			err = e.Stop(ctx, tc.args.name)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Stop(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestStopWatches(t *testing.T) {
+	type params struct {
+		mgr  manager.Manager
+		infs TrackingInformers
+		c    client.Client
+		opts []ControllerEngineOption
+	}
+	type args struct {
+		ctx  context.Context
+		name string
+		ws   []WatchID
+	}
+	type want struct {
+		stopped int
+		err     error
+		watches []WatchID
+	}
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"NoWatchesToStop": {
+			reason: "StopWatches should be a no-op when there's no watches to stop.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+					MockGetScheme: runtime.NewScheme,
+				},
+				infs: &MockTrackingInformers{
+					MockActiveInformers: func() []schema.GroupVersionKind {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx:  context.Background(),
+				name: "cool-controller",
+				ws: []WatchID{
+					{
+						Type: WatchTypeCompositeResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "NeverStarted",
+						},
+					},
+				},
+			},
+			want: want{
+				stopped: 0,
+				err:     nil,
+				watches: []WatchID{
+					{
+						Type: WatchTypeComposedResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+					{
+						Type: WatchTypeCompositeResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+				},
+			},
+		},
+		"StopOneWatch": {
+			reason: "StopWatches should only stop the watches it's asked to.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+					MockGetScheme: runtime.NewScheme,
+				},
+				infs: &MockTrackingInformers{
+					MockActiveInformers: func() []schema.GroupVersionKind {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx:  context.Background(),
+				name: "cool-controller",
+				ws: []WatchID{
+					{
+						Type: WatchTypeComposedResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+				},
+			},
+			want: want{
+				stopped: 1,
+				err:     nil,
+				watches: []WatchID{
+					{
+						Type: WatchTypeCompositeResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+				},
+			},
+		},
+		"StopAllWatches": {
+			reason: "StopWatches should stop all watches when asked to.",
+			params: params{
+				mgr: &MockManager{
+					MockElected: func() <-chan struct{} {
+						e := make(chan struct{})
+						close(e)
+						return e
+					},
+					MockGetScheme: runtime.NewScheme,
+				},
+				infs: &MockTrackingInformers{
+					MockActiveInformers: func() []schema.GroupVersionKind {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx:  context.Background(),
+				name: "cool-controller",
+				ws: []WatchID{
+					{
+						Type: WatchTypeComposedResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+					{
+						Type: WatchTypeCompositeResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "Resource",
+						},
+					},
+					{
+						Type: WatchTypeCompositeResource,
+						GVK: schema.GroupVersionKind{
+							Group:   "test.crossplane.io",
+							Version: "v1",
+							Kind:    "NeverStarted",
+						},
+					},
+				},
+			},
+			want: want{
+				stopped: 2,
+				err:     nil,
+				watches: []WatchID{},
+			},
+		},
+		// TODO(negz): Test handling watches that fail to stop? I'm not sure
+		// it's worth the amount of complexity making StoppableSource injectable
+		// would add. We could make Watch an interface with a GetSource.
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			e := New(tc.params.mgr, tc.params.infs, tc.params.c, tc.params.opts...)
+			err := e.Start(tc.args.name, WithNewControllerFn(func(_ string, _ manager.Manager, _ kcontroller.Options) (kcontroller.Controller, error) {
+				return &MockController{
+					MockStart: func(ctx context.Context) error {
+						<-ctx.Done()
+						return nil
+					},
+					MockWatch: func(_ source.Source) error {
+						return nil
+					},
+				}, nil
+			}))
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Fatalf("\n%s\ne.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Add some watches to stop.
+			u1 := &unstructured.Unstructured{}
+			u1.SetAPIVersion("test.crossplane.io/v1")
+			u1.SetKind("Resource")
+			err = e.StartWatches(tc.args.name,
+				WatchFor(u1, WatchTypeComposedResource, nil),
+				WatchFor(u1, WatchTypeCompositeResource, nil),
+			)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.StartWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			stopped, err := e.StopWatches(tc.args.ctx, tc.args.name, tc.args.ws...)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.StopWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.stopped, stopped); diff != "" {
+				t.Errorf("\n%s\ne.StopWatches(...): -want stopped, +got stopped:\n%s", tc.reason, diff)
+			}
+
+			watches, err := e.GetWatches(tc.args.name)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.GetWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.watches, watches,
+				cmpopts.EquateEmpty(),
+				cmpopts.SortSlices(func(a, b WatchID) bool { return fmt.Sprintf("%s", a) > fmt.Sprintf("%s", b) }),
+			); diff != "" {
+				t.Errorf("\n%s\ne.StartWatches(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			// Stop the controller. Will be a no-op if it never started.
+			err = e.Stop(tc.args.ctx, tc.args.name)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ne.Stop(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/engine/source_test.go b/internal/engine/source_test.go
new file mode 100644
index 000000000..e855c4ad9
--- /dev/null
+++ b/internal/engine/source_test.go
@@ -0,0 +1,272 @@
+/*
+Copyright 2020 The Crossplane 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 engine
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	kcache "k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/util/workqueue"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+)
+
+var _ cache.Informer = &MockInformer{}
+
+type MockInformer struct {
+	cache.Informer
+
+	MockAddEventHandler    func(handler kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error)
+	MockRemoveEventHandler func(handle kcache.ResourceEventHandlerRegistration) error
+}
+
+func (m *MockInformer) AddEventHandler(handler kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error) {
+	return m.MockAddEventHandler(handler)
+}
+
+func (m *MockInformer) RemoveEventHandler(handle kcache.ResourceEventHandlerRegistration) error {
+	return m.MockRemoveEventHandler(handle)
+}
+
+func TestStartSource(t *testing.T) {
+	type params struct {
+		infs cache.Informers
+		t    client.Object
+		h    handler.EventHandler
+		ps   []predicate.Predicate
+	}
+	type args struct {
+		ctx context.Context
+		q   workqueue.RateLimitingInterface
+	}
+	type want struct {
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"GetInformerError": {
+			reason: "Start should return an error if it can't get an informer for the supplied type.",
+			params: params{
+				infs: &MockCache{
+					MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+						return nil, errors.New("boom")
+					},
+				},
+				t: &unstructured.Unstructured{},
+			},
+			args: args{
+				ctx: context.Background(),
+				q:   nil, // Not called, just plumbed down to the event handler.
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"AddEventHandlerError": {
+			reason: "Start should return an error if it can't add an event handler to the informer.",
+			params: params{
+				infs: &MockCache{
+					MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+						return &MockInformer{
+							MockAddEventHandler: func(_ kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error) {
+								return nil, errors.New("boom")
+							},
+						}, nil
+					},
+				},
+				t: &unstructured.Unstructured{},
+			},
+			args: args{
+				ctx: context.Background(),
+				q:   nil, // Not called, just plumbed down to the event handler.
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"SuccessfulStart": {
+			reason: "Start should return nil if it successfully starts the source.",
+			params: params{
+				infs: &MockCache{
+					MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+						return &MockInformer{
+							MockAddEventHandler: func(_ kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error) {
+								return nil, nil
+							},
+						}, nil
+					},
+				},
+				t: &unstructured.Unstructured{},
+			},
+			args: args{
+				ctx: context.Background(),
+				q:   nil, // Not called, just plumbed down to the event handler.
+			},
+			want: want{
+				err: nil,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			s := NewStoppableSource(tc.params.infs, tc.params.t, tc.params.h, tc.params.ps...)
+
+			err := s.Start(tc.args.ctx, tc.args.q)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ns.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+var _ kcache.ResourceEventHandlerRegistration = &MockRegistration{}
+
+type MockRegistration struct{}
+
+func (m *MockRegistration) HasSynced() bool { return true }
+
+func TestStopSource(t *testing.T) {
+	type params struct {
+		infs cache.Informers
+		t    client.Object
+		h    handler.EventHandler
+		ps   []predicate.Predicate
+	}
+	type args struct {
+		ctx context.Context
+		q   workqueue.RateLimitingInterface
+	}
+	type want struct {
+		err error
+	}
+
+	// Used to return an error only when getting an informer to stop it.
+	started := false
+
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"SuccessfulStop": {
+			reason: "Stop should return nil if it successfully stops the source.",
+			params: params{
+				infs: &MockCache{
+					MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+						return &MockInformer{
+							MockAddEventHandler: func(_ kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error) {
+								return &MockRegistration{}, nil
+							},
+							MockRemoveEventHandler: func(_ kcache.ResourceEventHandlerRegistration) error {
+								return nil
+							},
+						}, nil
+					},
+				},
+				t: &unstructured.Unstructured{},
+			},
+			args: args{
+				ctx: context.Background(),
+				q:   nil, // Not called, just plumbed down to the event handler.
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"GetInformerError": {
+			reason: "Stop should return an error if it can't get an informer.",
+			params: params{
+				infs: &MockCache{
+					MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+						if !started {
+							started = true
+							return &MockInformer{
+								MockAddEventHandler: func(_ kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error) {
+									return &MockRegistration{}, nil
+								},
+							}, nil
+						}
+						return nil, errors.New("boom")
+					},
+				},
+				t: &unstructured.Unstructured{},
+			},
+			args: args{
+				ctx: context.Background(),
+				q:   nil, // Not called, just plumbed down to the event handler.
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+		"RemoveEventHandlerError": {
+			reason: "Stop should return an error if it can't remove the source's event handler.",
+			params: params{
+				infs: &MockCache{
+					MockGetInformer: func(_ context.Context, _ client.Object, _ ...cache.InformerGetOption) (cache.Informer, error) {
+						return &MockInformer{
+							MockAddEventHandler: func(_ kcache.ResourceEventHandler) (kcache.ResourceEventHandlerRegistration, error) {
+								return &MockRegistration{}, nil
+							},
+							MockRemoveEventHandler: func(_ kcache.ResourceEventHandlerRegistration) error {
+								return errors.New("boom")
+							},
+						}, nil
+					},
+				},
+				t: &unstructured.Unstructured{},
+			},
+			args: args{
+				ctx: context.Background(),
+				q:   nil, // Not called, just plumbed down to the event handler.
+			},
+			want: want{
+				err: cmpopts.AnyError,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			s := NewStoppableSource(tc.params.infs, tc.params.t, tc.params.h, tc.params.ps...)
+
+			err := s.Start(tc.args.ctx, tc.args.q)
+			if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ns.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+
+			err = s.Stop(tc.args.ctx)
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ns.Start(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/xpkg/fuzz_test.go b/internal/xpkg/fuzz_test.go
index ce989c349..359f08887 100644
--- a/internal/xpkg/fuzz_test.go
+++ b/internal/xpkg/fuzz_test.go
@@ -38,7 +38,7 @@ func FuzzFindXpkgInDir(f *testing.F) {
 				fs.Remove(createdFile)
 			}
 		}()
-		for i := 0; i < noOfFiles%500; i++ {
+		for range noOfFiles % 500 {
 			fname, err := ff.GetString()
 			if err != nil {
 				t.Skip()
diff --git a/pkg/validation/apiextensions/v1/composition/validator_test.go b/pkg/validation/apiextensions/v1/composition/validator_test.go
index c8723b3a2..84454e8cd 100644
--- a/pkg/validation/apiextensions/v1/composition/validator_test.go
+++ b/pkg/validation/apiextensions/v1/composition/validator_test.go
@@ -714,7 +714,6 @@ func defaultGKToCRDs() map[schema.GroupKind]apiextensions.CustomResourceDefiniti
 	crds := []apiextensions.CustomResourceDefinition{*defaultManagedCrdBuilder().build(), *defaultCompositeCrdBuilder().build()}
 	m := make(map[schema.GroupKind]apiextensions.CustomResourceDefinition, len(crds))
 	for _, crd := range crds {
-		crd := crd
 		m[schema.GroupKind{
 			Group: crd.Spec.Group,
 			Kind:  crd.Spec.Names.Kind,
diff --git a/test/e2e/funcs/feature.go b/test/e2e/funcs/feature.go
index ab96525ad..c84c9748e 100644
--- a/test/e2e/funcs/feature.go
+++ b/test/e2e/funcs/feature.go
@@ -1147,7 +1147,6 @@ func valueOrError(s string, err error) string {
 func itemsToObjects(items []unstructured.Unstructured) []client.Object {
 	objects := make([]client.Object, len(items))
 	for i, item := range items {
-		item := item // unalias loop variable
 		objects[i] = &item
 	}
 	return objects