Repository: crossplane/crossplane

Issue:
consider generating deterministic names for composed resources
After executing the function pipeline to compose resources, we can end up with a reconciliation storm due to the following sequence of events:

* desired composed resources are returned from the function pipeline
* these resources are given a random generated name: https://github.com/crossplane/crossplane/blob/a685f9065e7166b7299a92cbec5032f67c29464c/internal/controller/apiextensions/composite/composition_functions.go#L449
* we persist these names as references in the XR's spec: https://github.com/crossplane/crossplane/blob/a685f9065e7166b7299a92cbec5032f67c29464c/internal/controller/apiextensions/composite/composition_functions.go#L488-L494
* we loop through the composed resources to apply them: https://github.com/crossplane/crossplane/blob/a685f9065e7166b7299a92cbec5032f67c29464c/internal/controller/apiextensions/composite/composition_functions.go#L531
* if any fail, we stop the reconciliation: https://github.com/crossplane/crossplane/blob/a685f9065e7166b7299a92cbec5032f67c29464c/internal/controller/apiextensions/composite/composition_functions.go#L554
* reconciliation happens again immediately because we are watching for changes on the XR and we just updated the XR's spec
* we get different generated names and persist those to the XR spec again
* fail to apply composed resources again, reconcile again, repeat death spiral...

This scenario is mentioned in this comment: https://github.com/crossplane/crossplane/issues/6470#issuecomment-2934488135 

This is indicative of a systemic issue as mentioned by @n3wscott in https://github.com/crossplane/crossplane/issues/6481#issuecomment-3032873623. So using deterministic names that don't change every time will help with this specific scenario, but not be a cure all for over aggressive reconciliations in general.

Hints from issue comments:
@n3wscott: https://github.com/crossplane/crossplane/pull/6630 adds a test to show that ResourceRef is ignored (as expected...)

Solution patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 607f8152d..2cd723b25 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ on:
 
 env:
   # Common versions
-  EARTHLY_VERSION: '0.8.15'
+  EARTHLY_VERSION: '0.8.16'
 
   # Force Earthly to use color output
   FORCE_COLOR: "1"
@@ -257,9 +257,13 @@ jobs:
          - test-area: pkg
            test-suite: package-signature-verification
 
-        # Operations feature flags
+         # Operations feature flags
          - test-area: ops
-           test-suite: ops 
+           test-suite: ops
+
+         # Managed Resource Activation Policy (defaults disabled)
+         - test-area: mrap
+           test-suite: mrap
 
     steps:
       - name: Checkout
diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml
index 3f117bc7b..80ac9e75c 100644
--- a/.github/workflows/promote.yml
+++ b/.github/workflows/promote.yml
@@ -20,7 +20,7 @@ on:
 
 env:
   # Common versions
-  EARTHLY_VERSION: '0.8.15'
+  EARTHLY_VERSION: '0.8.16'
 
   # Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run
   # a step 'if env.AWS_USR' != ""', so we copy these to succinctly test whether
diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml
index 364f485a6..3672a9ac6 100644
--- a/.github/workflows/renovate.yml
+++ b/.github/workflows/renovate.yml
@@ -13,7 +13,7 @@ on:
 
 env:
   # Common versions
-  EARTHLY_VERSION: '0.8.15'
+  EARTHLY_VERSION: '0.8.16'
 
   LOG_LEVEL: "info"
 
diff --git a/.gitignore b/.gitignore
index 951b476e4..baaf8ac01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@ gitlab/
 # go build output (from go build ./cmd/crank etc)
 /crank
 /crossplane
+
+# ignore AI tools settings/config
+/.claude
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 97ee42495..7686a5a74 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -18,9 +18,9 @@ To add your organization to this list, you can choose any of the following optio
    [edit this file](https://github.com/crossplane/crossplane/edit/main/ADOPTERS.md) directly in
    Github
 1. Fill out the [adopters form](https://forms.gle/dBQhiyYkYSdzXovN6)
-1. Send an email to <steering@crossplane.io> with your information for the table below
+1. Send an email to <crossplane-steering@lists.cncf.io> with your information for the table below
 
-Feel free to reach out to <steering@crossplane.io> with any questions and/or assistance with
+Feel free to reach out to <crossplane-steering@lists.cncf.io> with any questions and/or assistance with
 updating this list.
 
 ## Crossplane Adopters
diff --git a/Earthfile b/Earthfile
index ff97f24a6..4661cb946 100644
--- a/Earthfile
+++ b/Earthfile
@@ -227,7 +227,7 @@ go-test:
 
 # go-lint lints Go code.
 go-lint:
-  ARG GOLANGCI_LINT_VERSION=v2.2.1
+  ARG GOLANGCI_LINT_VERSION=v2.2.2
   FROM +go-modules
   # This cache is private because golangci-lint doesn't support concurrent runs.
   CACHE --id go-lint --sharing private /root/.cache/golangci-lint
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index 864341818..296bd0050 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -62,13 +62,13 @@ alphabetical order of first name). When terms of the committee members expire,
 new members will be selected through the [election process](#election-process)
 outlined below.
 
-| &nbsp;                                                    | Member                                         | Organization | Email                       | Term Start | Term End   |
-|-----------------------------------------------------------|------------------------------------------------|--------------|-----------------------------|------------|------------|
-| <img width="30px" src="https://github.com/bassam.png">    | [Bassam Tabbara](https://github.com/bassam)    | Upbound      | bassam@upbound.io           | 2024-02-06 | 2026-02-06 |
-| <img width="30px" src="https://github.com/bobh66.png">    | [Bob Haddleton](https://github.com/bobh66)     | Nokia        | bob.haddleton@nokia.com     | 2025-02-06 | 2027-02-06 |
-| <img width="30px" src="https://github.com/lindblombr.png">| [Brian Lindblom](https://github.com/lindblombr)| Apple        | blindblom@apple.com         | 2025-02-06 | 2027-02-06 |
-| <img width="30px" src="https://github.com/jbw976.png">    | [Jared Watts](https://github.com/jbw976)       | Upbound      | jared@upbound.io            | 2024-02-06 | 2026-02-06 |
-| <img width="30px" src="https://github.com/negz.png">      | [Nic Cope](https://github.com/negz)            | Upbound      | negz@upbound.io             | 2024-02-06 | 2026-02-06 |
+| &nbsp;                                                     | Member                                          | Organization | Email                   | Term Start | Term End   |
+|------------------------------------------------------------|-------------------------------------------------|--------------|-------------------------|------------|------------|
+| <img width="30px" src="https://github.com/bassam.png">     | [Bassam Tabbara](https://github.com/bassam)     | Upbound      | bassam@upbound.io       | 2024-02-06 | 2026-02-06 |
+| <img width="30px" src="https://github.com/bobh66.png">     | [Bob Haddleton](https://github.com/bobh66)      | Nokia        | bob.haddleton@nokia.com | 2025-02-06 | 2027-02-06 |
+| <img width="30px" src="https://github.com/lindblombr.png"> | [Brian Lindblom](https://github.com/lindblombr) | Apple        | blindblom@apple.com     | 2025-02-06 | 2027-02-06 |
+| <img width="30px" src="https://github.com/jbw976.png">     | [Jared Watts](https://github.com/jbw976)        | Upbound      | jared@upbound.io        | 2024-02-06 | 2026-02-06 |
+| <img width="30px" src="https://github.com/negz.png">       | [Nic Cope](https://github.com/negz)             | Upbound      | negz@upbound.io         | 2024-02-06 | 2026-02-06 |
 
 ### Contact Info
 
@@ -76,7 +76,7 @@ The steering committee can be reached at the following locations:
 
 * [`#steering-committee`](https://crossplane.slack.com/archives/C032WMA459S)
   channel on the [Crossplane Slack](https://slack.crossplane.io/) workspace
-* [`steering@crossplane.io`](mailto:steering@crossplane.io) public email address
+* [`crossplane-steering@lists.cncf.io`](mailto:crossplane-steering@lists.cncf.io) public email address
 
 Members of the community as well as the broader ecosystem are welcome to contact
 the steering committee for any issues or concerns they can assist with.
diff --git a/README.md b/README.md
index abfee5038..3864ae445 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ documentation].
 
 | Release | Release Date  |   EOL    |
 |:-------:|:-------------:|:--------:|
-|  v1.18  | Nov 5, 2024   | Aug 2025 |
+|  v1.18  |  Nov 5, 2024  | Aug 2025 |
 |  v1.19  | Feb 11, 2025  | Nov 2025 |
 |  v1.20  | May 21, 2025  | Feb 2026 |
 |  v2.0   | Early Aug '25 | May 2026 |
@@ -66,8 +66,8 @@ a bug, suggest an improvement, or request a new feature please open an [issue
 against Crossplane] or the relevant provider. Refer to our [contributing guide]
 for more information on how you can help.
 
-* Discuss Crossplane on [Slack] or our [developer mailing list].
-* Follow us on [Bluesky], [Twitter], or [LinkedIn], or subscribe to our [newsletter].
+* Discuss Crossplane on [Slack].
+* Follow us on [Bluesky], [Twitter], or [LinkedIn].
 * Contact us via [Email].
 * Join our regular community meetings.
 * Provide feedback on our [roadmap and releases board].
@@ -80,7 +80,7 @@ Anyone who wants to discuss the direction of the project, design and
 implementation reviews, or raise general questions with the broader community is
 encouraged to join.
 
-* Meeting link: <https://zoom.us/j/425148449?pwd=NEk4N0tHWGpEazhuam1yR28yWHY5QT09>
+* Meeting link: <https://zoom-lfx.platform.linuxfoundation.org/meeting/98901510164?password=c60c41ae-1e1e-42d0-9a74-16de2fbb66f9>
 * [Current agenda and past meeting notes]
 * [Past meeting recordings]
 * [Community Calendar][community calendar]
@@ -120,12 +120,10 @@ Crossplane is under the Apache 2.0 license.
 [release cycle documentation]: https://docs.crossplane.io/knowledge-base/guides/release-cycle
 [install]: https://crossplane.io/docs/latest
 [Slack]: https://slack.crossplane.io
-[developer mailing list]: https://groups.google.com/forum/#!forum/crossplane-dev
 [Bluesky]: https://bsky.app/profile/crossplane.io
 [Twitter]: https://twitter.com/crossplane_io
 [LinkedIn]: https://www.linkedin.com/company/crossplane/
-[newsletter]: https://eepurl.com/ivy4v-/
-[Email]: mailto:info@crossplane.io
+[Email]: mailto:crossplane-info@lists.cncf.io
 [issue against Crossplane]: https://github.com/crossplane/crossplane/issues
 [contributing guide]: contributing/README.md
 [community meeting time]: https://www.thetimezoneconverter.com/?t=10:00&tz=PT%20%28Pacific%20Time%29
@@ -134,7 +132,7 @@ Crossplane is under the Apache 2.0 license.
 [roadmap and releases board]: https://github.com/orgs/crossplane/projects/20/views/9?pane=info
 [cncf]: https://www.cncf.io/
 [Get Started Docs]: https://docs.crossplane.io/latest/getting-started/
-[community calendar]: https://calendar.google.com/calendar/embed?src=c_2cdn0hs9e2m05rrv1233cjoj1k%40group.calendar.google.com
+[community calendar]: https://zoom-lfx.platform.linuxfoundation.org/meetings/crossplane?view=month
 [releases]: https://github.com/crossplane/crossplane/releases
 [ADOPTERS.md]: ADOPTERS.md
 [regular community meetings]: https://github.com/crossplane/crossplane/blob/main/README.md#get-involved
diff --git a/apis/apiextensions/apiextensions.go b/apis/apiextensions/apiextensions.go
index 88afbe8f5..fd9ded06d 100644
--- a/apis/apiextensions/apiextensions.go
+++ b/apis/apiextensions/apiextensions.go
@@ -23,6 +23,7 @@ import (
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
 	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
 	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
+	v2 "github.com/crossplane/crossplane/apis/apiextensions/v2"
 	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
 )
 
@@ -33,6 +34,7 @@ func init() {
 		v1beta1.AddToScheme,
 		v1alpha1.AddToScheme,
 		v2alpha1.AddToScheme,
+		v2.AddToScheme,
 	)
 }
 
diff --git a/apis/apiextensions/v1/composition_common.go b/apis/apiextensions/v1/composition_common.go
index ea21594e8..6811d8fab 100644
--- a/apis/apiextensions/v1/composition_common.go
+++ b/apis/apiextensions/v1/composition_common.go
@@ -77,6 +77,13 @@ type PipelineStep struct {
 	// +listType=map
 	// +listMapKey=name
 	Credentials []FunctionCredentials `json:"credentials,omitempty"`
+
+	// Requirements are resource requirements that will be satisfied before
+	// this pipeline step is called for the first time. This allows
+	// pre-populating required resources without requiring a function to
+	// request them first.
+	// +optional
+	Requirements *FunctionRequirements `json:"requirements,omitempty"`
 }
 
 // A FunctionReference references a function that may be used in a
@@ -117,3 +124,42 @@ const (
 	// credentials from a secret.
 	FunctionCredentialsSourceSecret FunctionCredentialsSource = "Secret"
 )
+
+// FunctionRequirements define requirements that a function may need to
+// satisfy.
+type FunctionRequirements struct {
+	// RequiredResources is a list of resources that must be fetched before
+	// this function is called.
+	// +optional
+	// +listType=map
+	// +listMapKey=requirementName
+	RequiredResources []RequiredResourceSelector `json:"requiredResources,omitempty"`
+}
+
+// RequiredResourceSelector selects a required resource.
+//
+// +kubebuilder:validation:XValidation:rule="(has(self.name) && !has(self.matchLabels)) || (!has(self.name) && has(self.matchLabels))",message="Either name or matchLabels must be specified, but not both"
+type RequiredResourceSelector struct {
+	// RequirementName is the unique name to identify this required resource
+	// in the Required Resources map in the function request.
+	RequirementName string `json:"requirementName"`
+
+	// APIVersion of the required resource.
+	APIVersion string `json:"apiVersion"`
+
+	// Kind of the required resource.
+	Kind string `json:"kind"`
+
+	// Namespace of the required resource if it is namespaced.
+	// +optional
+	Namespace *string `json:"namespace,omitempty"`
+
+	// Name of the required resource.
+	// +optional
+	Name *string `json:"name,omitempty"`
+
+	// MatchLabels specifies the set of labels to match for finding the
+	// required resource. When specified, Name is ignored.
+	// +optional
+	MatchLabels map[string]string `json:"matchLabels,omitempty"`
+}
diff --git a/apis/apiextensions/v1/xrd_types.go b/apis/apiextensions/v1/xrd_types.go
index a14843510..2de2d306a 100644
--- a/apis/apiextensions/v1/xrd_types.go
+++ b/apis/apiextensions/v1/xrd_types.go
@@ -252,6 +252,7 @@ type CompositeResourceDefinitionControllerStatus struct {
 // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
 // +kubebuilder:subresource:status
 // +kubebuilder:resource:scope=Cluster,categories=crossplane,shortName=xrd;xrds
+// +kubebuilder:deprecatedversion:warning="CompositeResourceDefinition v1 is deprecated and will be removed in a future release; consider migrating to v2"
 type CompositeResourceDefinition struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
diff --git a/apis/apiextensions/v1/zz_generated.conversion.go b/apis/apiextensions/v1/zz_generated.conversion.go
index 27546ede0..6af11b441 100644
--- a/apis/apiextensions/v1/zz_generated.conversion.go
+++ b/apis/apiextensions/v1/zz_generated.conversion.go
@@ -50,6 +50,20 @@ func (c *GeneratedRevisionSpecConverter) pRuntimeRawExtensionToPRuntimeRawExtens
 	}
 	return pRuntimeRawExtension
 }
+func (c *GeneratedRevisionSpecConverter) pV1FunctionRequirementsToPV1FunctionRequirements(source *FunctionRequirements) *FunctionRequirements {
+	var pV1FunctionRequirements *FunctionRequirements
+	if source != nil {
+		var v1FunctionRequirements FunctionRequirements
+		if (*source).RequiredResources != nil {
+			v1FunctionRequirements.RequiredResources = make([]RequiredResourceSelector, len((*source).RequiredResources))
+			for i := 0; i < len((*source).RequiredResources); i++ {
+				v1FunctionRequirements.RequiredResources[i] = c.v1RequiredResourceSelectorToV1RequiredResourceSelector((*source).RequiredResources[i])
+			}
+		}
+		pV1FunctionRequirements = &v1FunctionRequirements
+	}
+	return pV1FunctionRequirements
+}
 func (c *GeneratedRevisionSpecConverter) pV1SecretReferenceToPV1SecretReference(source *v1.SecretReference) *v1.SecretReference {
 	var pV1SecretReference *v1.SecretReference
 	if source != nil {
@@ -103,8 +117,30 @@ func (c *GeneratedRevisionSpecConverter) v1PipelineStepToV1PipelineStep(source P
 			v1PipelineStep.Credentials[i] = c.v1FunctionCredentialsToV1FunctionCredentials(source.Credentials[i])
 		}
 	}
+	v1PipelineStep.Requirements = c.pV1FunctionRequirementsToPV1FunctionRequirements(source.Requirements)
 	return v1PipelineStep
 }
+func (c *GeneratedRevisionSpecConverter) v1RequiredResourceSelectorToV1RequiredResourceSelector(source RequiredResourceSelector) RequiredResourceSelector {
+	var v1RequiredResourceSelector RequiredResourceSelector
+	v1RequiredResourceSelector.RequirementName = source.RequirementName
+	v1RequiredResourceSelector.APIVersion = source.APIVersion
+	v1RequiredResourceSelector.Kind = source.Kind
+	if source.Namespace != nil {
+		xstring := *source.Namespace
+		v1RequiredResourceSelector.Namespace = &xstring
+	}
+	if source.Name != nil {
+		xstring2 := *source.Name
+		v1RequiredResourceSelector.Name = &xstring2
+	}
+	if source.MatchLabels != nil {
+		v1RequiredResourceSelector.MatchLabels = make(map[string]string, len(source.MatchLabels))
+		for key, value := range source.MatchLabels {
+			v1RequiredResourceSelector.MatchLabels[key] = value
+		}
+	}
+	return v1RequiredResourceSelector
+}
 func (c *GeneratedRevisionSpecConverter) v1TypeReferenceToV1TypeReference(source TypeReference) TypeReference {
 	var v1TypeReference TypeReference
 	v1TypeReference.APIVersion = source.APIVersion
diff --git a/apis/apiextensions/v1/zz_generated.deepcopy.go b/apis/apiextensions/v1/zz_generated.deepcopy.go
index a74cf89f0..e8a9eb7a4 100644
--- a/apis/apiextensions/v1/zz_generated.deepcopy.go
+++ b/apis/apiextensions/v1/zz_generated.deepcopy.go
@@ -506,6 +506,28 @@ func (in *FunctionReference) DeepCopy() *FunctionReference {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FunctionRequirements) DeepCopyInto(out *FunctionRequirements) {
+	*out = *in
+	if in.RequiredResources != nil {
+		in, out := &in.RequiredResources, &out.RequiredResources
+		*out = make([]RequiredResourceSelector, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionRequirements.
+func (in *FunctionRequirements) DeepCopy() *FunctionRequirements {
+	if in == nil {
+		return nil
+	}
+	out := new(FunctionRequirements)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GeneratedRevisionSpecConverter) DeepCopyInto(out *GeneratedRevisionSpecConverter) {
 	*out = *in
@@ -537,6 +559,11 @@ func (in *PipelineStep) DeepCopyInto(out *PipelineStep) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Requirements != nil {
+		in, out := &in.Requirements, &out.Requirements
+		*out = new(FunctionRequirements)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStep.
@@ -549,6 +576,38 @@ func (in *PipelineStep) DeepCopy() *PipelineStep {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RequiredResourceSelector) DeepCopyInto(out *RequiredResourceSelector) {
+	*out = *in
+	if in.Namespace != nil {
+		in, out := &in.Namespace, &out.Namespace
+		*out = new(string)
+		**out = **in
+	}
+	if in.Name != nil {
+		in, out := &in.Name, &out.Name
+		*out = new(string)
+		**out = **in
+	}
+	if in.MatchLabels != nil {
+		in, out := &in.MatchLabels, &out.MatchLabels
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequiredResourceSelector.
+func (in *RequiredResourceSelector) DeepCopy() *RequiredResourceSelector {
+	if in == nil {
+		return nil
+	}
+	out := new(RequiredResourceSelector)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TypeReference) DeepCopyInto(out *TypeReference) {
 	*out = *in
diff --git a/apis/apiextensions/v2/doc.go b/apis/apiextensions/v2/doc.go
new file mode 100644
index 000000000..99ff9f2cd
--- /dev/null
+++ b/apis/apiextensions/v2/doc.go
@@ -0,0 +1,21 @@
+/*
+Copyright 2025 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 v2 contains API types that extend the Crossplane API.
+// +kubebuilder:object:generate=true
+// +groupName=apiextensions.crossplane.io
+// +versionName=v2
+package v2
diff --git a/apis/apiextensions/v2/register.go b/apis/apiextensions/v2/register.go
new file mode 100644
index 000000000..c66580cee
--- /dev/null
+++ b/apis/apiextensions/v2/register.go
@@ -0,0 +1,55 @@
+/*
+Copyright 2025 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 v2
+
+import (
+	"reflect"
+
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+// Package type metadata.
+const (
+	Group   = "apiextensions.crossplane.io"
+	Version = "v2"
+)
+
+var (
+	// SchemeGroupVersion is group version used to register these objects.
+	SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version}
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+	SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
+
+	// AddToScheme adds all registered types to scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)
+
+// CompositeResourceDefinition type metadata.
+var (
+	CompositeResourceDefinitionKind             = reflect.TypeOf(CompositeResourceDefinition{}).Name()
+	CompositeResourceDefinitionGroupKind        = schema.GroupKind{Group: Group, Kind: CompositeResourceDefinitionKind}.String()
+	CompositeResourceDefinitionKindAPIVersion   = CompositeResourceDefinitionKind + "." + SchemeGroupVersion.String()
+	CompositeResourceDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(CompositeResourceDefinitionKind)
+)
+
+func init() {
+	SchemeBuilder.Register(
+		&CompositeResourceDefinition{}, &CompositeResourceDefinitionList{},
+	)
+}
diff --git a/apis/apiextensions/v2alpha1/xrd_types.go b/apis/apiextensions/v2/xrd_types.go
similarity index 99%
rename from apis/apiextensions/v2alpha1/xrd_types.go
rename to apis/apiextensions/v2/xrd_types.go
index c0c8aab66..1bee4ba1d 100644
--- a/apis/apiextensions/v2alpha1/xrd_types.go
+++ b/apis/apiextensions/v2/xrd_types.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2020 The Crossplane Authors.
+Copyright 2025 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.
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v2alpha1
+package v2
 
 import (
 	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -271,7 +271,6 @@ type CompositeResourceDefinitionList struct {
 // the composite resource this CompositeResourceDefinition defines.
 func (c *CompositeResourceDefinition) GetCompositeGroupVersionKind() schema.GroupVersionKind {
 	v := ""
-
 	for _, vr := range c.Spec.Versions {
 		if vr.Referenceable {
 			v = vr.Name
@@ -297,7 +296,6 @@ func (c *CompositeResourceDefinition) GetClaimGroupVersionKind() schema.GroupVer
 	}
 
 	v := ""
-
 	for _, vr := range c.Spec.Versions {
 		if vr.Referenceable {
 			v = vr.Name
diff --git a/apis/apiextensions/v2/zz_generated.deepcopy.go b/apis/apiextensions/v2/zz_generated.deepcopy.go
new file mode 100644
index 000000000..32b1ebfc0
--- /dev/null
+++ b/apis/apiextensions/v2/zz_generated.deepcopy.go
@@ -0,0 +1,293 @@
+//go:build !ignore_autogenerated
+
+/*
+Copyright 2021 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.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v2
+
+import (
+	"github.com/crossplane/crossplane-runtime/apis/common/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinition) DeepCopyInto(out *CompositeResourceDefinition) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinition.
+func (in *CompositeResourceDefinition) DeepCopy() *CompositeResourceDefinition {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinition)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CompositeResourceDefinition) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinitionControllerStatus) DeepCopyInto(out *CompositeResourceDefinitionControllerStatus) {
+	*out = *in
+	out.CompositeResourceTypeRef = in.CompositeResourceTypeRef
+	out.CompositeResourceClaimTypeRef = in.CompositeResourceClaimTypeRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionControllerStatus.
+func (in *CompositeResourceDefinitionControllerStatus) DeepCopy() *CompositeResourceDefinitionControllerStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinitionControllerStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinitionList) DeepCopyInto(out *CompositeResourceDefinitionList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]CompositeResourceDefinition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionList.
+func (in *CompositeResourceDefinitionList) DeepCopy() *CompositeResourceDefinitionList {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinitionList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CompositeResourceDefinitionList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinitionSpec) DeepCopyInto(out *CompositeResourceDefinitionSpec) {
+	*out = *in
+	in.Names.DeepCopyInto(&out.Names)
+	if in.DefaultCompositionRef != nil {
+		in, out := &in.DefaultCompositionRef, &out.DefaultCompositionRef
+		*out = new(CompositionReference)
+		**out = **in
+	}
+	if in.EnforcedCompositionRef != nil {
+		in, out := &in.EnforcedCompositionRef, &out.EnforcedCompositionRef
+		*out = new(CompositionReference)
+		**out = **in
+	}
+	if in.DefaultCompositionUpdatePolicy != nil {
+		in, out := &in.DefaultCompositionUpdatePolicy, &out.DefaultCompositionUpdatePolicy
+		*out = new(v1.UpdatePolicy)
+		**out = **in
+	}
+	if in.Versions != nil {
+		in, out := &in.Versions, &out.Versions
+		*out = make([]CompositeResourceDefinitionVersion, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.Conversion != nil {
+		in, out := &in.Conversion, &out.Conversion
+		*out = new(apiextensionsv1.CustomResourceConversion)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Metadata != nil {
+		in, out := &in.Metadata, &out.Metadata
+		*out = new(CompositeResourceDefinitionSpecMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClaimNames != nil {
+		in, out := &in.ClaimNames, &out.ClaimNames
+		*out = new(apiextensionsv1.CustomResourceDefinitionNames)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.DefaultCompositeDeletePolicy != nil {
+		in, out := &in.DefaultCompositeDeletePolicy, &out.DefaultCompositeDeletePolicy
+		*out = new(v1.CompositeDeletePolicy)
+		**out = **in
+	}
+	if in.ConnectionSecretKeys != nil {
+		in, out := &in.ConnectionSecretKeys, &out.ConnectionSecretKeys
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionSpec.
+func (in *CompositeResourceDefinitionSpec) DeepCopy() *CompositeResourceDefinitionSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinitionSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinitionSpecMetadata) DeepCopyInto(out *CompositeResourceDefinitionSpecMetadata) {
+	*out = *in
+	if in.Labels != nil {
+		in, out := &in.Labels, &out.Labels
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.Annotations != nil {
+		in, out := &in.Annotations, &out.Annotations
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionSpecMetadata.
+func (in *CompositeResourceDefinitionSpecMetadata) DeepCopy() *CompositeResourceDefinitionSpecMetadata {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinitionSpecMetadata)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinitionStatus) DeepCopyInto(out *CompositeResourceDefinitionStatus) {
+	*out = *in
+	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+	out.Controllers = in.Controllers
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionStatus.
+func (in *CompositeResourceDefinitionStatus) DeepCopy() *CompositeResourceDefinitionStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinitionStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceDefinitionVersion) DeepCopyInto(out *CompositeResourceDefinitionVersion) {
+	*out = *in
+	if in.Deprecated != nil {
+		in, out := &in.Deprecated, &out.Deprecated
+		*out = new(bool)
+		**out = **in
+	}
+	if in.DeprecationWarning != nil {
+		in, out := &in.DeprecationWarning, &out.DeprecationWarning
+		*out = new(string)
+		**out = **in
+	}
+	if in.Schema != nil {
+		in, out := &in.Schema, &out.Schema
+		*out = new(CompositeResourceValidation)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.AdditionalPrinterColumns != nil {
+		in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
+		*out = make([]apiextensionsv1.CustomResourceColumnDefinition, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionVersion.
+func (in *CompositeResourceDefinitionVersion) DeepCopy() *CompositeResourceDefinitionVersion {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceDefinitionVersion)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositeResourceValidation) DeepCopyInto(out *CompositeResourceValidation) {
+	*out = *in
+	in.OpenAPIV3Schema.DeepCopyInto(&out.OpenAPIV3Schema)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceValidation.
+func (in *CompositeResourceValidation) DeepCopy() *CompositeResourceValidation {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositeResourceValidation)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CompositionReference) DeepCopyInto(out *CompositionReference) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositionReference.
+func (in *CompositionReference) DeepCopy() *CompositionReference {
+	if in == nil {
+		return nil
+	}
+	out := new(CompositionReference)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TypeReference) DeepCopyInto(out *TypeReference) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeReference.
+func (in *TypeReference) DeepCopy() *TypeReference {
+	if in == nil {
+		return nil
+	}
+	out := new(TypeReference)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/apis/apiextensions/v2alpha1/conditions.go b/apis/apiextensions/v2alpha1/conditions.go
index 4b9d77efb..febb54bcd 100644
--- a/apis/apiextensions/v2alpha1/conditions.go
+++ b/apis/apiextensions/v2alpha1/conditions.go
@@ -29,60 +29,114 @@ const (
 	// started a controller to reconcile instances of said resource.
 	TypeEstablished xpv1.ConditionType = "Established"
 
-	// A TypeOffered XRD has created the CRD for its composite resource claim
-	// and started a controller to reconcile instances of said claim.
-	TypeOffered xpv1.ConditionType = "Offered"
+	// A TypeHealthy indicates the resource is healthy and working.
+	TypeHealthy xpv1.ConditionType = "Healthy"
 )
 
-// Reasons a resource is or is not established or offered.
+// Reasons a resource is or is not healthy, established or offered.
 const (
-	ReasonWatchingComposite xpv1.ConditionReason = "WatchingCompositeResource"
-	ReasonWatchingClaim     xpv1.ConditionReason = "WatchingCompositeResourceClaim"
-
-	ReasonTerminatingComposite xpv1.ConditionReason = "TerminatingCompositeResource"
-	ReasonTerminatingClaim     xpv1.ConditionReason = "TerminatingCompositeResourceClaim"
+	ReasonHealthy                     xpv1.ConditionReason = "Running"
+	ReasonUnhealthy                   xpv1.ConditionReason = "EncounteredErrors"
+	EstablishedManagedResource        xpv1.ConditionReason = "EstablishedManagedResource"
+	ReasonPendingManaged              xpv1.ConditionReason = "PendingManagedResource"
+	ReasonInactiveManaged             xpv1.ConditionReason = "InactiveManagedResource"
+	ReasonBlockedActivationPolicy     xpv1.ConditionReason = "BlockedManagedResourceActivationPolicy"
+	ReasonTerminatingManaged          xpv1.ConditionReason = "TerminatingManagedResource"
+	ReasonTerminatingActivationPolicy xpv1.ConditionReason = "TerminatingManagedResourceActivationPolicy"
 )
 
-// WatchingComposite indicates that Crossplane has defined and is watching for a
-// new kind of composite resource.
-func WatchingComposite() xpv1.Condition {
+// EstablishedManaged indicates that Crossplane has defined new kind of managed resource.
+func EstablishedManaged() xpv1.Condition {
 	return xpv1.Condition{
 		Type:               TypeEstablished,
 		Status:             corev1.ConditionTrue,
 		LastTransitionTime: metav1.Now(),
-		Reason:             ReasonWatchingComposite,
+		Reason:             EstablishedManagedResource,
 	}
 }
 
-// TerminatingComposite indicates that Crossplane is terminating the controller
-// for and removing the definition of a composite resource.
-func TerminatingComposite() xpv1.Condition {
+// InactiveManaged indicates this managed resource is in the inactive state.
+func InactiveManaged() xpv1.Condition {
 	return xpv1.Condition{
 		Type:               TypeEstablished,
 		Status:             corev1.ConditionFalse,
 		LastTransitionTime: metav1.Now(),
-		Reason:             ReasonTerminatingComposite,
+		Reason:             ReasonInactiveManaged,
 	}
 }
 
-// WatchingClaim indicates that Crossplane has defined and is watching for a
-// new kind of composite resource claim.
-func WatchingClaim() xpv1.Condition {
+// PendingManaged indicates that Crossplane has defined and is waiting for a
+// new kind of managed resource to become accepted.
+func PendingManaged() xpv1.Condition {
 	return xpv1.Condition{
-		Type:               TypeOffered,
+		Type:               TypeEstablished,
+		Status:             corev1.ConditionUnknown,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonPendingManaged,
+	}
+}
+
+// BlockedManaged indicates that Crossplane has encountered an error attempting to
+// reconcile a managed resource definition.
+func BlockedManaged() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeEstablished,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonPendingManaged,
+	}
+}
+
+// Healthy indicates that the controller is running as expected.
+func Healthy() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeHealthy,
 		Status:             corev1.ConditionTrue,
 		LastTransitionTime: metav1.Now(),
-		Reason:             ReasonWatchingClaim,
+		Reason:             ReasonHealthy,
+	}
+}
+
+// Unhealthy indicates that the controller is running into issues.
+func Unhealthy() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeHealthy,
+		Status:             corev1.ConditionUnknown,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonUnhealthy,
+	}
+}
+
+// BlockedActivationPolicy indicates that Crossplane is blocked attempting to
+// reconcile of a managed resource activation policy.
+func BlockedActivationPolicy() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeEstablished,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonBlockedActivationPolicy,
 	}
 }
 
-// TerminatingClaim indicates that Crossplane is terminating the controller and
-// removing the definition of a composite resource claim.
-func TerminatingClaim() xpv1.Condition {
+// TerminatingActivationPolicy indicates that Crossplane is terminating the
+// controller for and removing the definition of a managed resource activation
+// policy.
+func TerminatingActivationPolicy() xpv1.Condition {
 	return xpv1.Condition{
-		Type:               TypeOffered,
+		Type:               TypeEstablished,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonTerminatingActivationPolicy,
+	}
+}
+
+// TerminatingManaged indicates that Crossplane is terminating the controller
+// for and removing the definition of a managed resource.
+func TerminatingManaged() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeEstablished,
 		Status:             corev1.ConditionFalse,
 		LastTransitionTime: metav1.Now(),
-		Reason:             ReasonTerminatingClaim,
+		Reason:             ReasonTerminatingManaged,
 	}
 }
diff --git a/apis/apiextensions/v2alpha1/crd_types.go b/apis/apiextensions/v2alpha1/crd_types.go
index 4edfbf162..70b83da11 100644
--- a/apis/apiextensions/v2alpha1/crd_types.go
+++ b/apis/apiextensions/v2alpha1/crd_types.go
@@ -38,11 +38,18 @@ type CustomResourceDefinitionSpec struct {
 	// Group is the API group of the defined custom resource.
 	// The custom resources are served under `/apis/<group>/...`.
 	// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
 	Group string `json:"group"`
 	// Names specify the resource and kind names for the custom resource.
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
+	// +kubebuilder:validation:XValidation:rule="self.plural == self.plural.lowerAscii()",message="Plural name must be lowercase"
+	// +kubebuilder:validation:XValidation:rule="!has(self.singular) || self.singular == self.singular.lowerAscii()",message="Singular name must be lowercase"
 	Names extv1.CustomResourceDefinitionNames `json:"names"`
 	// Scope indicates whether the defined custom resource is cluster- or namespace-scoped.
 	// Allowed values are `Cluster` and `Namespaced`.
+	// +kubebuilder:validation:Enum=Namespaced;Cluster
+	// +kubebuilder:default=Namespaced
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
 	Scope extv1.ResourceScope `json:"scope"`
 	// Versions is the list of all API versions of the defined custom resource.
 	// Version names are used to compute the order in which served versions are listed in API discovery.
@@ -78,11 +85,12 @@ type CustomResourceDefinitionVersion struct {
 	// Storage indicates this version should be used when persisting custom resources to storage.
 	// There must be exactly one version with storage=true.
 	Storage bool `json:"storage"`
-	// Voldemort indicates this version of the custom resource API is deprecated.
+	//nolint:gocritic // the field is called deprecated, it isn't deprecated itself
+	// Deprecated indicates this version of the custom resource API is deprecated.
 	// When set to true, API requests to this version receive a warning header in the server response.
 	// Defaults to false.
 	// +optional
-	Voldemort bool `json:"deprecated,omitempty"`
+	Deprecated bool `json:"deprecated,omitempty"`
 	// DeprecationWarning overrides the default warning returned to API clients.
 	// May only be set when `deprecated` is true.
 	// The default warning indicates this version is deprecated and recommends use
diff --git a/apis/apiextensions/v2alpha1/doc.go b/apis/apiextensions/v2alpha1/doc.go
index cdb801e0f..1c6d64665 100644
--- a/apis/apiextensions/v2alpha1/doc.go
+++ b/apis/apiextensions/v2alpha1/doc.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2020 The Crossplane Authors.
+Copyright 2025 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.
diff --git a/apis/apiextensions/v2alpha1/mrd_policy_types.go b/apis/apiextensions/v2alpha1/mrd_policy_types.go
index 7b340eaa0..bc44bfd34 100644
--- a/apis/apiextensions/v2alpha1/mrd_policy_types.go
+++ b/apis/apiextensions/v2alpha1/mrd_policy_types.go
@@ -17,6 +17,9 @@ limitations under the License.
 package v2alpha1
 
 import (
+	"path/filepath"
+	"slices"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -25,11 +28,17 @@ import (
 // ActivationPolicy matches on MRD names with wildcard prefix support.
 type ActivationPolicy string
 
+// Match reports whether name matches the activation policy pattern.
+func (a ActivationPolicy) Match(name string) bool {
+	match, _ := filepath.Match(string(a), name)
+	return match
+}
+
 // ManagedResourceActivationPolicySpec specifies the desired activation state of ManagedResourceDefinitions.
 type ManagedResourceActivationPolicySpec struct {
 	// Activations is an array of MRD names to activate. Supports wildcard
 	// prefixes (like `*.aws.crossplane.io`) but not full regular expressions.
-	// +required
+	// +kubebuilder:validation:MinItems=1
 	Activations []ActivationPolicy `json:"activate"`
 }
 
@@ -42,6 +51,22 @@ type ManagedResourceActivationPolicyStatus struct {
 	Activated []string `json:"activated,omitempty"`
 }
 
+// ClearActivated sets the activated list to nil.
+func (m *ManagedResourceActivationPolicyStatus) ClearActivated() {
+	m.Activated = nil
+}
+
+// AppendActivated adds name to the activated list and maintains sorted order.
+func (m *ManagedResourceActivationPolicyStatus) AppendActivated(name string) {
+	if m.Activated == nil {
+		m.Activated = []string{name}
+		return
+	}
+	m.Activated = append(m.Activated, name)
+	// Make sure to keep Activated ordered.
+	slices.Sort(m.Activated)
+}
+
 // +kubebuilder:object:root=true
 // +genclient
 // +genclient:nonNamespaced
@@ -59,6 +84,26 @@ type ManagedResourceActivationPolicy struct {
 	Status ManagedResourceActivationPolicyStatus `json:"status,omitempty"`
 }
 
+// GetCondition of this ManagedResourceActivationPolicy.
+func (p *ManagedResourceActivationPolicy) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return p.Status.GetCondition(ct)
+}
+
+// SetConditions of this ManagedResourceActivationPolicy.
+func (p *ManagedResourceActivationPolicy) SetConditions(c ...xpv1.Condition) {
+	p.Status.SetConditions(c...)
+}
+
+// Activates returns if a given name would be activated based on spec.activations.
+func (p *ManagedResourceActivationPolicy) Activates(name string) bool {
+	for _, a := range p.Spec.Activations {
+		if a.Match(name) {
+			return true
+		}
+	}
+	return false
+}
+
 // +kubebuilder:object:root=true
 
 // ManagedResourceActivationPolicyList contains a list of ManagedResourceActivationPolicy.
diff --git a/apis/apiextensions/v2alpha1/mrd_types.go b/apis/apiextensions/v2alpha1/mrd_types.go
index 313e84add..f5058cb3d 100644
--- a/apis/apiextensions/v2alpha1/mrd_types.go
+++ b/apis/apiextensions/v2alpha1/mrd_types.go
@@ -31,7 +31,10 @@ type ManagedResourceDefinitionSpec struct {
 	ConnectionDetails []ConnectionDetail `json:"connectionDetails,omitempty"`
 
 	// State toggles whether the underlying CRD is created or not.
-	State ManagedResourceDefinitionState `json:"state"`
+	// +kubebuilder:validation:Enum=Active;Inactive
+	// +kubebuilder:default=Inactive
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf || oldSelf != 'Active'",message="state cannot be changed once it becomes Active"
+	State ManagedResourceDefinitionState `json:"state,omitempty"`
 }
 
 // ManagedResourceDefinitionState is the state of the resource definition.
@@ -45,6 +48,11 @@ const (
 	ManagedResourceDefinitionInactive ManagedResourceDefinitionState = "Inactive"
 )
 
+// IsActive returns if this ManagedResourceDefinitionState is "Active".
+func (s ManagedResourceDefinitionState) IsActive() bool {
+	return s == ManagedResourceDefinitionActive
+}
+
 // ConnectionDetail holds keys and descriptions of connection secrets.
 type ConnectionDetail struct {
 	// Name of the key.
@@ -77,6 +85,16 @@ type ManagedResourceDefinition struct {
 	Status ManagedResourceDefinitionStatus `json:"status,omitempty"`
 }
 
+// GetCondition of this ManagedResourceDefinition.
+func (p *ManagedResourceDefinition) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return p.Status.GetCondition(ct)
+}
+
+// SetConditions of this ManagedResourceDefinition.
+func (p *ManagedResourceDefinition) SetConditions(c ...xpv1.Condition) {
+	p.Status.SetConditions(c...)
+}
+
 // +kubebuilder:object:root=true
 
 // ManagedResourceDefinitionList contains a list of ManagedResourceDefinitions.
diff --git a/apis/apiextensions/v2alpha1/register.go b/apis/apiextensions/v2alpha1/register.go
index 56385b1fd..ab8791349 100644
--- a/apis/apiextensions/v2alpha1/register.go
+++ b/apis/apiextensions/v2alpha1/register.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2020 The Crossplane Authors.
+Copyright 2025 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.
@@ -42,11 +42,6 @@ var (
 
 // CompositeResourceDefinition type metadata.
 var (
-	CompositeResourceDefinitionKind             = reflect.TypeOf(CompositeResourceDefinition{}).Name()
-	CompositeResourceDefinitionGroupKind        = schema.GroupKind{Group: Group, Kind: CompositeResourceDefinitionKind}.String()
-	CompositeResourceDefinitionKindAPIVersion   = CompositeResourceDefinitionKind + "." + SchemeGroupVersion.String()
-	CompositeResourceDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(CompositeResourceDefinitionKind)
-
 	ManagedResourceDefinitionKind             = reflect.TypeOf(ManagedResourceDefinition{}).Name()
 	ManagedResourceDefinitionGroupKind        = schema.GroupKind{Group: Group, Kind: ManagedResourceDefinitionKind}.String()
 	ManagedResourceDefinitionKindAPIVersion   = ManagedResourceDefinitionKind + "." + SchemeGroupVersion.String()
@@ -60,7 +55,6 @@ var (
 
 func init() {
 	SchemeBuilder.Register(
-		&CompositeResourceDefinition{}, &CompositeResourceDefinitionList{},
 		&ManagedResourceDefinition{}, &ManagedResourceDefinitionList{},
 		&ManagedResourceActivationPolicy{}, &ManagedResourceActivationPolicyList{},
 	)
diff --git a/apis/apiextensions/v2alpha1/zz_generated.deepcopy.go b/apis/apiextensions/v2alpha1/zz_generated.deepcopy.go
index 90533d151..ae9a5d25d 100644
--- a/apis/apiextensions/v2alpha1/zz_generated.deepcopy.go
+++ b/apis/apiextensions/v2alpha1/zz_generated.deepcopy.go
@@ -21,262 +21,10 @@ limitations under the License.
 package v2alpha1
 
 import (
-	commonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinition) DeepCopyInto(out *CompositeResourceDefinition) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-	in.Status.DeepCopyInto(&out.Status)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinition.
-func (in *CompositeResourceDefinition) DeepCopy() *CompositeResourceDefinition {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinition)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *CompositeResourceDefinition) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinitionControllerStatus) DeepCopyInto(out *CompositeResourceDefinitionControllerStatus) {
-	*out = *in
-	out.CompositeResourceTypeRef = in.CompositeResourceTypeRef
-	out.CompositeResourceClaimTypeRef = in.CompositeResourceClaimTypeRef
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionControllerStatus.
-func (in *CompositeResourceDefinitionControllerStatus) DeepCopy() *CompositeResourceDefinitionControllerStatus {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinitionControllerStatus)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinitionList) DeepCopyInto(out *CompositeResourceDefinitionList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]CompositeResourceDefinition, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionList.
-func (in *CompositeResourceDefinitionList) DeepCopy() *CompositeResourceDefinitionList {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinitionList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *CompositeResourceDefinitionList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinitionSpec) DeepCopyInto(out *CompositeResourceDefinitionSpec) {
-	*out = *in
-	in.Names.DeepCopyInto(&out.Names)
-	if in.DefaultCompositionRef != nil {
-		in, out := &in.DefaultCompositionRef, &out.DefaultCompositionRef
-		*out = new(CompositionReference)
-		**out = **in
-	}
-	if in.EnforcedCompositionRef != nil {
-		in, out := &in.EnforcedCompositionRef, &out.EnforcedCompositionRef
-		*out = new(CompositionReference)
-		**out = **in
-	}
-	if in.DefaultCompositionUpdatePolicy != nil {
-		in, out := &in.DefaultCompositionUpdatePolicy, &out.DefaultCompositionUpdatePolicy
-		*out = new(commonv1.UpdatePolicy)
-		**out = **in
-	}
-	if in.Versions != nil {
-		in, out := &in.Versions, &out.Versions
-		*out = make([]CompositeResourceDefinitionVersion, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-	if in.Conversion != nil {
-		in, out := &in.Conversion, &out.Conversion
-		*out = new(v1.CustomResourceConversion)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.Metadata != nil {
-		in, out := &in.Metadata, &out.Metadata
-		*out = new(CompositeResourceDefinitionSpecMetadata)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.ClaimNames != nil {
-		in, out := &in.ClaimNames, &out.ClaimNames
-		*out = new(v1.CustomResourceDefinitionNames)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.DefaultCompositeDeletePolicy != nil {
-		in, out := &in.DefaultCompositeDeletePolicy, &out.DefaultCompositeDeletePolicy
-		*out = new(commonv1.CompositeDeletePolicy)
-		**out = **in
-	}
-	if in.ConnectionSecretKeys != nil {
-		in, out := &in.ConnectionSecretKeys, &out.ConnectionSecretKeys
-		*out = make([]string, len(*in))
-		copy(*out, *in)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionSpec.
-func (in *CompositeResourceDefinitionSpec) DeepCopy() *CompositeResourceDefinitionSpec {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinitionSpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinitionSpecMetadata) DeepCopyInto(out *CompositeResourceDefinitionSpecMetadata) {
-	*out = *in
-	if in.Labels != nil {
-		in, out := &in.Labels, &out.Labels
-		*out = make(map[string]string, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-	if in.Annotations != nil {
-		in, out := &in.Annotations, &out.Annotations
-		*out = make(map[string]string, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionSpecMetadata.
-func (in *CompositeResourceDefinitionSpecMetadata) DeepCopy() *CompositeResourceDefinitionSpecMetadata {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinitionSpecMetadata)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinitionStatus) DeepCopyInto(out *CompositeResourceDefinitionStatus) {
-	*out = *in
-	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
-	out.Controllers = in.Controllers
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionStatus.
-func (in *CompositeResourceDefinitionStatus) DeepCopy() *CompositeResourceDefinitionStatus {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinitionStatus)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceDefinitionVersion) DeepCopyInto(out *CompositeResourceDefinitionVersion) {
-	*out = *in
-	if in.Deprecated != nil {
-		in, out := &in.Deprecated, &out.Deprecated
-		*out = new(bool)
-		**out = **in
-	}
-	if in.DeprecationWarning != nil {
-		in, out := &in.DeprecationWarning, &out.DeprecationWarning
-		*out = new(string)
-		**out = **in
-	}
-	if in.Schema != nil {
-		in, out := &in.Schema, &out.Schema
-		*out = new(CompositeResourceValidation)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.AdditionalPrinterColumns != nil {
-		in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
-		*out = make([]v1.CustomResourceColumnDefinition, len(*in))
-		copy(*out, *in)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceDefinitionVersion.
-func (in *CompositeResourceDefinitionVersion) DeepCopy() *CompositeResourceDefinitionVersion {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceDefinitionVersion)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositeResourceValidation) DeepCopyInto(out *CompositeResourceValidation) {
-	*out = *in
-	in.OpenAPIV3Schema.DeepCopyInto(&out.OpenAPIV3Schema)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositeResourceValidation.
-func (in *CompositeResourceValidation) DeepCopy() *CompositeResourceValidation {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositeResourceValidation)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CompositionReference) DeepCopyInto(out *CompositionReference) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompositionReference.
-func (in *CompositionReference) DeepCopy() *CompositionReference {
-	if in == nil {
-		return nil
-	}
-	out := new(CompositionReference)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ConnectionDetail) DeepCopyInto(out *ConnectionDetail) {
 	*out = *in
@@ -571,18 +319,3 @@ func (in *ManagedResourceDefinitionStatus) DeepCopy() *ManagedResourceDefinition
 	in.DeepCopyInto(out)
 	return out
 }
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *TypeReference) DeepCopyInto(out *TypeReference) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeReference.
-func (in *TypeReference) DeepCopy() *TypeReference {
-	if in == nil {
-		return nil
-	}
-	out := new(TypeReference)
-	in.DeepCopyInto(out)
-	return out
-}
diff --git a/apis/ops/v1alpha1/common.go b/apis/ops/v1alpha1/common.go
new file mode 100644
index 000000000..6ed395ff8
--- /dev/null
+++ b/apis/ops/v1alpha1/common.go
@@ -0,0 +1,54 @@
+/*
+Copyright 2025 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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// ConcurrencyPolicy specifies how to treat concurrent executions of an
+// operation.
+type ConcurrencyPolicy string
+
+const (
+	// ConcurrencyPolicyAllow allows concurrent executions.
+	ConcurrencyPolicyAllow ConcurrencyPolicy = "Allow"
+
+	// ConcurrencyPolicyForbid forbids concurrent executions, skipping the next
+	// run if the previous run hasn't finished yet.
+	ConcurrencyPolicyForbid ConcurrencyPolicy = "Forbid"
+
+	// ConcurrencyPolicyReplace replaces the currently running operation with a
+	// new one.
+	ConcurrencyPolicyReplace ConcurrencyPolicy = "Replace"
+)
+
+// OperationTemplate is a template for creating an Operation.
+type OperationTemplate struct {
+	// Standard object metadata.
+	// +optional
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	// Spec is the specification of the Operation to be created.
+	Spec OperationSpec `json:"spec"`
+}
+
+// A RunningOperationRef is a reference to a running operation.
+type RunningOperationRef struct {
+	// Name of the active operation.
+	Name string `json:"name"`
+}
diff --git a/apis/ops/v1alpha1/conditions.go b/apis/ops/v1alpha1/conditions.go
index 232e7bff0..03ec6ffb4 100644
--- a/apis/ops/v1alpha1/conditions.go
+++ b/apis/ops/v1alpha1/conditions.go
@@ -27,6 +27,14 @@ const (
 
 	// A TypeValidPipeline Operation has a valid function pipeline.
 	TypeValidPipeline xpv1.ConditionType = "ValidPipeline"
+
+	// A TypeWatching condition indicates whether a WatchOperation is
+	// actively watching resources.
+	TypeWatching xpv1.ConditionType = "Watching"
+
+	// A TypeScheduling condition indicates whether a CronOperation is
+	// actively scheduling operations.
+	TypeScheduling xpv1.ConditionType = "Scheduling"
 )
 
 // Reasons a package is or is not installed.
@@ -37,6 +45,14 @@ const (
 
 	ReasonValidPipeline       xpv1.ConditionReason = "ValidPipeline"
 	ReasonMissingCapabilities xpv1.ConditionReason = "MissingCapabilities"
+
+	ReasonWatchActive xpv1.ConditionReason = "WatchActive"
+	ReasonWatchFailed xpv1.ConditionReason = "WatchFailed"
+	ReasonWatchPaused xpv1.ConditionReason = "WatchPaused"
+
+	ReasonScheduleActive  xpv1.ConditionReason = "ScheduleActive"
+	ReasonScheduleInvalid xpv1.ConditionReason = "ScheduleInvalid"
+	ReasonSchedulePaused  xpv1.ConditionReason = "SchedulePaused"
 )
 
 // Running indicates that an operation is running.
@@ -91,3 +107,69 @@ func MissingCapabilities(message string) xpv1.Condition {
 		Message:            message,
 	}
 }
+
+// WatchActive indicates that a WatchOperation is actively watching resources.
+func WatchActive() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeWatching,
+		Status:             corev1.ConditionTrue,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonWatchActive,
+	}
+}
+
+// WatchFailed indicates that a WatchOperation failed to establish or maintain
+// its watch.
+func WatchFailed(message string) xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeWatching,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonWatchFailed,
+		Message:            message,
+	}
+}
+
+// WatchPaused indicates that a WatchOperation is paused and not
+// actively watching resources.
+func WatchPaused() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeWatching,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonWatchPaused,
+	}
+}
+
+// ScheduleActive indicates that a CronOperation is actively scheduling
+// operations.
+func ScheduleActive() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeScheduling,
+		Status:             corev1.ConditionTrue,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonScheduleActive,
+	}
+}
+
+// ScheduleInvalid indicates that a CronOperation has an invalid cron schedule.
+func ScheduleInvalid(message string) xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeScheduling,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonScheduleInvalid,
+		Message:            message,
+	}
+}
+
+// SchedulePaused indicates that a CronOperation is paused and not
+// actively scheduling operations.
+func SchedulePaused() xpv1.Condition {
+	return xpv1.Condition{
+		Type:               TypeScheduling,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             ReasonSchedulePaused,
+	}
+}
diff --git a/apis/ops/v1alpha1/cronoperation_types.go b/apis/ops/v1alpha1/cronoperation_types.go
new file mode 100644
index 000000000..44cf4fdf9
--- /dev/null
+++ b/apis/ops/v1alpha1/cronoperation_types.go
@@ -0,0 +1,120 @@
+/*
+Copyright 2025 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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+)
+
+// LabelCronOperationName is the label Crossplane adds to Operations to
+// represent the CronOperation that created them.
+const LabelCronOperationName = "ops.crossplane.io/cronoperation"
+
+// CronOperationSpec specifies the desired state of a CronOperation.
+type CronOperationSpec struct {
+	// Schedule is the cron schedule for the operation.
+	Schedule string `json:"schedule"`
+
+	// StartingDeadlineSeconds is the deadline in seconds for starting the
+	// operation if it misses its scheduled time for any reason.
+	// +optional
+	StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty"`
+
+	// ConcurrencyPolicy specifies how to treat concurrent executions of an
+	// operation.
+	// +optional
+	// +kubebuilder:default=Allow
+	// +kubebuilder:validation:Enum=Allow;Forbid;Replace
+	ConcurrencyPolicy *ConcurrencyPolicy `json:"concurrencyPolicy,omitempty"`
+
+	// SuccessfulHistoryLimit is the number of successful Operations to retain.
+	// +optional
+	// +kubebuilder:default=3
+	SuccessfulHistoryLimit *int32 `json:"successfulHistoryLimit,omitempty"`
+
+	// FailedHistoryLimit is the number of failed Operations to retain.
+	// +optional
+	// +kubebuilder:default=1
+	FailedHistoryLimit *int32 `json:"failedHistoryLimit,omitempty"`
+
+	// OperationTemplate is the template for the Operation to be created.
+	OperationTemplate OperationTemplate `json:"operationTemplate"`
+}
+
+// CronOperationStatus represents the observed state of a CronOperation.
+type CronOperationStatus struct {
+	xpv1.ConditionedStatus `json:",inline"`
+
+	// RunningOperationRefs is a list of currently running Operations.
+	// +optional
+	RunningOperationRefs []RunningOperationRef `json:"runningOperationRefs,omitempty"`
+
+	// LastScheduleTime is the last time the CronOperation was scheduled.
+	// +optional
+	LastScheduleTime *metav1.Time `json:"lastScheduleTime,omitempty"`
+
+	// LastSuccessfulTime is the last time the CronOperation was successfully
+	// completed.
+	// +optional
+	LastSuccessfulTime *metav1.Time `json:"lastSuccessfulTime,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +genclient
+
+// A CronOperation creates Operations on a cron schedule.
+//
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="SCHEDULE",type="string",JSONPath=".spec.schedule"
+// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status"
+// +kubebuilder:printcolumn:name="SCHEDULING",type="string",JSONPath=".status.conditions[?(@.type=='Scheduling')].status"
+// +kubebuilder:printcolumn:name="LAST SCHEDULE",type="date",JSONPath=".status.lastScheduleTime"
+// +kubebuilder:printcolumn:name="LAST SUCCESS",type="date",JSONPath=".status.lastSuccessfulTime"
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:resource:scope=Cluster,categories=crossplane,shortName=cronops
+type CronOperation struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   CronOperationSpec   `json:"spec,omitempty"`
+	Status CronOperationStatus `json:"status,omitempty"`
+}
+
+// SetConditions delegates to Status.SetConditions.
+// Implements Conditioned.SetConditions.
+func (co *CronOperation) SetConditions(cs ...xpv1.Condition) {
+	co.Status.SetConditions(cs...)
+}
+
+// GetCondition delegates to Status.GetCondition.
+// Implements Conditioned.GetCondition.
+func (co *CronOperation) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return co.Status.GetCondition(ct)
+}
+
+// +kubebuilder:object:root=true
+
+// CronOperationList contains a list of CronOperations.
+type CronOperationList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+
+	Items []CronOperation `json:"items"`
+}
diff --git a/apis/ops/v1alpha1/operation_types.go b/apis/ops/v1alpha1/operation_types.go
index 7c6009028..4d04d7f8d 100644
--- a/apis/ops/v1alpha1/operation_types.go
+++ b/apis/ops/v1alpha1/operation_types.go
@@ -45,6 +45,10 @@ type OperationSpec struct {
 
 	// Pipeline is a list of operation function steps that will be used when
 	// this operation runs.
+	// +listType=map
+	// +listMapKey=step
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=99
 	Pipeline []PipelineStep `json:"pipeline"`
 
 	// RetryLimit configures how many times the operation may fail. When the
@@ -73,7 +77,16 @@ type PipelineStep struct {
 
 	// Credentials are optional credentials that the operation function needs.
 	// +optional
+	// +listType=map
+	// +listMapKey=name
 	Credentials []FunctionCredentials `json:"credentials,omitempty"`
+
+	// Requirements are resource requirements that will be satisfied before
+	// this pipeline step is called for the first time. This allows
+	// pre-populating required resources without requiring a function to
+	// request them first.
+	// +optional
+	Requirements *FunctionRequirements `json:"requirements,omitempty"`
 }
 
 // A FunctionReference references an operation function that may be used in an
@@ -113,6 +126,45 @@ const (
 	FunctionCredentialsSourceSecret FunctionCredentialsSource = "Secret"
 )
 
+// FunctionRequirements specifies resource requirements for a pipeline step.
+type FunctionRequirements struct {
+	// RequiredResources that will be fetched before this pipeline step
+	// is called for the first time.
+	// +optional
+	// +listType=map
+	// +listMapKey=requirementName
+	RequiredResources []RequiredResourceSelector `json:"requiredResources,omitempty"`
+}
+
+// RequiredResourceSelector selects resources that should be fetched before
+// a pipeline step runs.
+// +kubebuilder:validation:XValidation:rule="(has(self.name) && !has(self.matchLabels)) || (!has(self.name) && has(self.matchLabels))",message="Either name or matchLabels must be specified, but not both"
+type RequiredResourceSelector struct {
+	// RequirementName uniquely identifies this group of resources.
+	// This name will be used as the key in RunFunctionRequest.required_resources.
+	RequirementName string `json:"requirementName"`
+
+	// APIVersion of resources to select.
+	APIVersion string `json:"apiVersion"`
+
+	// Kind of resources to select.
+	Kind string `json:"kind"`
+
+	// Name matches a single resource by name. Only one of Name or
+	// MatchLabels may be specified.
+	// +optional
+	Name *string `json:"name,omitempty"`
+
+	// MatchLabels matches resources by label selector. Only one of Name or
+	// MatchLabels may be specified.
+	// +optional
+	MatchLabels map[string]string `json:"matchLabels,omitempty"`
+
+	// Namespace to search for resources. Optional for cluster-scoped resources.
+	// +optional
+	Namespace *string `json:"namespace,omitempty"`
+}
+
 // OperationStatus represents the observed state of an operation.
 type OperationStatus struct {
 	xpv1.ConditionedStatus `json:",inline"`
@@ -176,8 +228,9 @@ func (r *AppliedResourceRef) Equals(other AppliedResourceRef) bool {
 // two operation.
 //
 // +kubebuilder:subresource:status
-// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status"
 // +kubebuilder:printcolumn:name="SUCCEEDED",type="string",JSONPath=".status.conditions[?(@.type=='Succeeded')].status"
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
 // +kubebuilder:resource:scope=Cluster,categories=crossplane,shortName=ops
 type Operation struct {
 	metav1.TypeMeta   `json:",inline"`
diff --git a/apis/ops/v1alpha1/register.go b/apis/ops/v1alpha1/register.go
index 3667170b5..d26dd15d4 100644
--- a/apis/ops/v1alpha1/register.go
+++ b/apis/ops/v1alpha1/register.go
@@ -45,6 +45,24 @@ var (
 	OperationGroupVersionKind = SchemeGroupVersion.WithKind(OperationKind)
 )
 
+// CronOperation type metadata.
+var (
+	CronOperationKind             = reflect.TypeOf(CronOperation{}).Name()
+	CronOperationGroupKind        = schema.GroupKind{Group: Group, Kind: CronOperationKind}.String()
+	CronOperationKindAPIVersion   = CronOperationKind + "." + SchemeGroupVersion.String()
+	CronOperationGroupVersionKind = SchemeGroupVersion.WithKind(CronOperationKind)
+)
+
+// WatchOperation type metadata.
+var (
+	WatchOperationKind             = reflect.TypeOf(WatchOperation{}).Name()
+	WatchOperationGroupKind        = schema.GroupKind{Group: Group, Kind: WatchOperationKind}.String()
+	WatchOperationKindAPIVersion   = WatchOperationKind + "." + SchemeGroupVersion.String()
+	WatchOperationGroupVersionKind = SchemeGroupVersion.WithKind(WatchOperationKind)
+)
+
 func init() {
 	SchemeBuilder.Register(&Operation{}, &OperationList{})
+	SchemeBuilder.Register(&CronOperation{}, &CronOperationList{})
+	SchemeBuilder.Register(&WatchOperation{}, &WatchOperationList{})
 }
diff --git a/apis/ops/v1alpha1/watchoperation_types.go b/apis/ops/v1alpha1/watchoperation_types.go
new file mode 100644
index 000000000..c3c4e5aab
--- /dev/null
+++ b/apis/ops/v1alpha1/watchoperation_types.go
@@ -0,0 +1,161 @@
+/*
+Copyright 2025 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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+)
+
+// LabelWatchOperationName is the label Crossplane adds to Operations to
+// represent the WatchOperation that created them.
+const LabelWatchOperationName = "ops.crossplane.io/watchoperation"
+
+// Annotations that Crossplane adds to Operations to represent information
+// about the watched resource that triggered the Operation.
+const (
+	AnnotationWatchedResourceAPIVersion      = "ops.crossplane.io/watched-resource-apiversion"
+	AnnotationWatchedResourceKind            = "ops.crossplane.io/watched-resource-kind"
+	AnnotationWatchedResourceName            = "ops.crossplane.io/watched-resource-name"
+	AnnotationWatchedResourceNamespace       = "ops.crossplane.io/watched-resource-namespace"
+	AnnotationWatchedResourceResourceVersion = "ops.crossplane.io/watched-resource-resourceversion"
+)
+
+// SyntheticResourceVersionDeleted is used as the ResourceVersion for
+// synthetic watched resources when the actual resource has been deleted.
+const SyntheticResourceVersionDeleted = "ops.crossplane.io/synthetic-deleted"
+
+// RequirementNameWatchedResource is the requirement name used by WatchOperations
+// to inject the watched resource into Operations they create.
+const RequirementNameWatchedResource = "ops.crossplane.io/watched-resource"
+
+// WatchOperationSpec specifies the desired state of a WatchOperation.
+type WatchOperationSpec struct {
+	// Watch specifies the resource to watch.
+	Watch WatchSpec `json:"watch"`
+
+	// ConcurrencyPolicy specifies how to treat concurrent executions of an
+	// operation.
+	// +optional
+	// +kubebuilder:default=Allow
+	// +kubebuilder:validation:Enum=Allow;Forbid;Replace
+	ConcurrencyPolicy *ConcurrencyPolicy `json:"concurrencyPolicy,omitempty"`
+
+	// SuccessfulHistoryLimit is the number of successful Operations to retain.
+	// +optional
+	// +kubebuilder:default=3
+	SuccessfulHistoryLimit *int32 `json:"successfulHistoryLimit,omitempty"`
+
+	// FailedHistoryLimit is the number of failed Operations to retain.
+	// +optional
+	// +kubebuilder:default=1
+	FailedHistoryLimit *int32 `json:"failedHistoryLimit,omitempty"`
+
+	// OperationTemplate is the template for the Operation to be created.
+	OperationTemplate OperationTemplate `json:"operationTemplate"`
+}
+
+// WatchSpec specifies what resource to watch.
+type WatchSpec struct {
+	// APIVersion of the resource to watch.
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="apiVersion is immutable"
+	APIVersion string `json:"apiVersion"`
+
+	// Kind of the resource to watch.
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="kind is immutable"
+	Kind string `json:"kind"`
+
+	// MatchLabels selects resources by label. If empty, all resources of the
+	// specified kind are watched.
+	// +optional
+	MatchLabels map[string]string `json:"matchLabels,omitempty"`
+
+	// Namespace selects resources in a specific namespace. If empty, all
+	// namespaces are watched. Only applicable for namespaced resources.
+	// +optional
+	Namespace string `json:"namespace,omitempty"`
+}
+
+// WatchOperationStatus represents the observed state of a WatchOperation.
+type WatchOperationStatus struct {
+	xpv1.ConditionedStatus `json:",inline"`
+
+	// RunningOperationRefs is a list of currently running Operations.
+	// +optional
+	RunningOperationRefs []RunningOperationRef `json:"runningOperationRefs,omitempty"`
+
+	// WatchingResources is the number of resources this WatchOperation is
+	// currently watching.
+	// +optional
+	WatchingResources int64 `json:"watchingResources,omitempty"`
+
+	// LastScheduleTime is the last time the WatchOperation created an
+	// Operation.
+	// +optional
+	LastScheduleTime *metav1.Time `json:"lastScheduleTime,omitempty"`
+
+	// LastSuccessfulTime is the last time the WatchOperation successfully
+	// completed an Operation.
+	// +optional
+	LastSuccessfulTime *metav1.Time `json:"lastSuccessfulTime,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +genclient
+
+// A WatchOperation creates Operations when watched resources change.
+//
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="KIND",type="string",JSONPath=".spec.watch.kind"
+// +kubebuilder:printcolumn:name="COUNT",type="integer",JSONPath=".status.watchingResources"
+// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status"
+// +kubebuilder:printcolumn:name="WATCHING",type="string",JSONPath=".status.conditions[?(@.type=='Watching')].status"
+// +kubebuilder:printcolumn:name="LAST SCHEDULE",type="date",JSONPath=".status.lastScheduleTime"
+// +kubebuilder:printcolumn:name="LAST SUCCESS",type="date",JSONPath=".status.lastSuccessfulTime"
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:resource:scope=Cluster,categories=crossplane,shortName=watchops
+type WatchOperation struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   WatchOperationSpec   `json:"spec,omitempty"`
+	Status WatchOperationStatus `json:"status,omitempty"`
+}
+
+// SetConditions delegates to Status.SetConditions.
+// Implements Conditioned.SetConditions.
+func (wo *WatchOperation) SetConditions(cs ...xpv1.Condition) {
+	wo.Status.SetConditions(cs...)
+}
+
+// GetCondition delegates to Status.GetCondition.
+// Implements Conditioned.GetCondition.
+func (wo *WatchOperation) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return wo.Status.GetCondition(ct)
+}
+
+// +kubebuilder:object:root=true
+
+// WatchOperationList contains a list of WatchOperations.
+type WatchOperationList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+
+	Items []WatchOperation `json:"items"`
+}
diff --git a/apis/ops/v1alpha1/zz_generated.deepcopy.go b/apis/ops/v1alpha1/zz_generated.deepcopy.go
index f509aaa4e..ad3279ab2 100644
--- a/apis/ops/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/ops/v1alpha1/zz_generated.deepcopy.go
@@ -45,6 +45,130 @@ func (in *AppliedResourceRef) DeepCopy() *AppliedResourceRef {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CronOperation) DeepCopyInto(out *CronOperation) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronOperation.
+func (in *CronOperation) DeepCopy() *CronOperation {
+	if in == nil {
+		return nil
+	}
+	out := new(CronOperation)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CronOperation) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CronOperationList) DeepCopyInto(out *CronOperationList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]CronOperation, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronOperationList.
+func (in *CronOperationList) DeepCopy() *CronOperationList {
+	if in == nil {
+		return nil
+	}
+	out := new(CronOperationList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CronOperationList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CronOperationSpec) DeepCopyInto(out *CronOperationSpec) {
+	*out = *in
+	if in.StartingDeadlineSeconds != nil {
+		in, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds
+		*out = new(int64)
+		**out = **in
+	}
+	if in.ConcurrencyPolicy != nil {
+		in, out := &in.ConcurrencyPolicy, &out.ConcurrencyPolicy
+		*out = new(ConcurrencyPolicy)
+		**out = **in
+	}
+	if in.SuccessfulHistoryLimit != nil {
+		in, out := &in.SuccessfulHistoryLimit, &out.SuccessfulHistoryLimit
+		*out = new(int32)
+		**out = **in
+	}
+	if in.FailedHistoryLimit != nil {
+		in, out := &in.FailedHistoryLimit, &out.FailedHistoryLimit
+		*out = new(int32)
+		**out = **in
+	}
+	in.OperationTemplate.DeepCopyInto(&out.OperationTemplate)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronOperationSpec.
+func (in *CronOperationSpec) DeepCopy() *CronOperationSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(CronOperationSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CronOperationStatus) DeepCopyInto(out *CronOperationStatus) {
+	*out = *in
+	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+	if in.RunningOperationRefs != nil {
+		in, out := &in.RunningOperationRefs, &out.RunningOperationRefs
+		*out = make([]RunningOperationRef, len(*in))
+		copy(*out, *in)
+	}
+	if in.LastScheduleTime != nil {
+		in, out := &in.LastScheduleTime, &out.LastScheduleTime
+		*out = (*in).DeepCopy()
+	}
+	if in.LastSuccessfulTime != nil {
+		in, out := &in.LastSuccessfulTime, &out.LastSuccessfulTime
+		*out = (*in).DeepCopy()
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronOperationStatus.
+func (in *CronOperationStatus) DeepCopy() *CronOperationStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(CronOperationStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *FunctionCredentials) DeepCopyInto(out *FunctionCredentials) {
 	*out = *in
@@ -80,6 +204,28 @@ func (in *FunctionReference) DeepCopy() *FunctionReference {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FunctionRequirements) DeepCopyInto(out *FunctionRequirements) {
+	*out = *in
+	if in.RequiredResources != nil {
+		in, out := &in.RequiredResources, &out.RequiredResources
+		*out = make([]RequiredResourceSelector, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionRequirements.
+func (in *FunctionRequirements) DeepCopy() *FunctionRequirements {
+	if in == nil {
+		return nil
+	}
+	out := new(FunctionRequirements)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Operation) DeepCopyInto(out *Operation) {
 	*out = *in
@@ -196,6 +342,23 @@ func (in *OperationStatus) DeepCopy() *OperationStatus {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OperationTemplate) DeepCopyInto(out *OperationTemplate) {
+	*out = *in
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperationTemplate.
+func (in *OperationTemplate) DeepCopy() *OperationTemplate {
+	if in == nil {
+		return nil
+	}
+	out := new(OperationTemplate)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PipelineStep) DeepCopyInto(out *PipelineStep) {
 	*out = *in
@@ -212,6 +375,11 @@ func (in *PipelineStep) DeepCopyInto(out *PipelineStep) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Requirements != nil {
+		in, out := &in.Requirements, &out.Requirements
+		*out = new(FunctionRequirements)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStep.
@@ -243,3 +411,192 @@ func (in *PipelineStepStatus) DeepCopy() *PipelineStepStatus {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RequiredResourceSelector) DeepCopyInto(out *RequiredResourceSelector) {
+	*out = *in
+	if in.Name != nil {
+		in, out := &in.Name, &out.Name
+		*out = new(string)
+		**out = **in
+	}
+	if in.MatchLabels != nil {
+		in, out := &in.MatchLabels, &out.MatchLabels
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.Namespace != nil {
+		in, out := &in.Namespace, &out.Namespace
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequiredResourceSelector.
+func (in *RequiredResourceSelector) DeepCopy() *RequiredResourceSelector {
+	if in == nil {
+		return nil
+	}
+	out := new(RequiredResourceSelector)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RunningOperationRef) DeepCopyInto(out *RunningOperationRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunningOperationRef.
+func (in *RunningOperationRef) DeepCopy() *RunningOperationRef {
+	if in == nil {
+		return nil
+	}
+	out := new(RunningOperationRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WatchOperation) DeepCopyInto(out *WatchOperation) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchOperation.
+func (in *WatchOperation) DeepCopy() *WatchOperation {
+	if in == nil {
+		return nil
+	}
+	out := new(WatchOperation)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *WatchOperation) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WatchOperationList) DeepCopyInto(out *WatchOperationList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]WatchOperation, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchOperationList.
+func (in *WatchOperationList) DeepCopy() *WatchOperationList {
+	if in == nil {
+		return nil
+	}
+	out := new(WatchOperationList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *WatchOperationList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WatchOperationSpec) DeepCopyInto(out *WatchOperationSpec) {
+	*out = *in
+	in.Watch.DeepCopyInto(&out.Watch)
+	if in.ConcurrencyPolicy != nil {
+		in, out := &in.ConcurrencyPolicy, &out.ConcurrencyPolicy
+		*out = new(ConcurrencyPolicy)
+		**out = **in
+	}
+	if in.SuccessfulHistoryLimit != nil {
+		in, out := &in.SuccessfulHistoryLimit, &out.SuccessfulHistoryLimit
+		*out = new(int32)
+		**out = **in
+	}
+	if in.FailedHistoryLimit != nil {
+		in, out := &in.FailedHistoryLimit, &out.FailedHistoryLimit
+		*out = new(int32)
+		**out = **in
+	}
+	in.OperationTemplate.DeepCopyInto(&out.OperationTemplate)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchOperationSpec.
+func (in *WatchOperationSpec) DeepCopy() *WatchOperationSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(WatchOperationSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WatchOperationStatus) DeepCopyInto(out *WatchOperationStatus) {
+	*out = *in
+	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+	if in.RunningOperationRefs != nil {
+		in, out := &in.RunningOperationRefs, &out.RunningOperationRefs
+		*out = make([]RunningOperationRef, len(*in))
+		copy(*out, *in)
+	}
+	if in.LastScheduleTime != nil {
+		in, out := &in.LastScheduleTime, &out.LastScheduleTime
+		*out = (*in).DeepCopy()
+	}
+	if in.LastSuccessfulTime != nil {
+		in, out := &in.LastSuccessfulTime, &out.LastSuccessfulTime
+		*out = (*in).DeepCopy()
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchOperationStatus.
+func (in *WatchOperationStatus) DeepCopy() *WatchOperationStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(WatchOperationStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WatchSpec) DeepCopyInto(out *WatchSpec) {
+	*out = *in
+	if in.MatchLabels != nil {
+		in, out := &in.MatchLabels, &out.MatchLabels
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchSpec.
+func (in *WatchSpec) DeepCopy() *WatchSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(WatchSpec)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/apis/pkg/meta/v1/capabilities.go b/apis/pkg/meta/v1/capabilities.go
index 7a328b7f9..428afeec7 100644
--- a/apis/pkg/meta/v1/capabilities.go
+++ b/apis/pkg/meta/v1/capabilities.go
@@ -16,13 +16,42 @@ limitations under the License.
 
 package v1
 
+import (
+	"regexp"
+	"strings"
+)
+
 // Known function capabilities. This shouldn't be treated as an exhaustive list.
 // A package's capabilities array may contain arbitrary entries that aren't
 // meaningful to Crossplane but might be meaningful to some other consumer.
 const (
-	// A function that can be used in a composition.
+	// FunctionCapabilityComposition is a capability key for a function that
+	// can be used in a composition.
 	FunctionCapabilityComposition = "composition"
 
-	// A function that can be used in an operation.
+	// FunctionCapabilityOperation is a capability key for function that can be
+	// used in an operation.
 	FunctionCapabilityOperation = "operation"
+
+	// ProviderCapabilitySafeStart is a capability key for a provider that
+	// supports "safe" starting of its controller gated on the existence of
+	// dependent kinds in the cluster.
+	ProviderCapabilitySafeStart = "safe-start"
 )
+
+// CapabilitiesContainFuzzyMatch will look in the list of capabilities and fuzzy match it to the given key.
+// Fuzzy match is defined as stripping all non-alphanumeric characters and ignoring case.
+func CapabilitiesContainFuzzyMatch(capabilities []string, key string) bool {
+	// fuz will match only alphanumeric characters.
+	fuz := regexp.MustCompile("[^a-zA-Z0-9]+")
+	key = fuz.ReplaceAllString(key, "")
+	key = strings.ToLower(key)
+	for _, c := range capabilities {
+		c = fuz.ReplaceAllString(c, "")
+		c = strings.ToLower(c)
+		if c == key {
+			return true
+		}
+	}
+	return false
+}
diff --git a/apis/pkg/meta/v1/meta.go b/apis/pkg/meta/v1/meta.go
index 624925681..c8e57e156 100644
--- a/apis/pkg/meta/v1/meta.go
+++ b/apis/pkg/meta/v1/meta.go
@@ -52,7 +52,7 @@ type Dependency struct {
 
 	// Package OCI reference of the dependency. Only used when apiVersion and
 	// kind are set.
-	// Musth be a fully qualified image name, including the registry,
+	// Must be a fully qualified image name, including the registry,
 	// repository, and tag. For example, "registry.example.com/repo/package:tag".
 	// +optional
 	Package *string `json:"package,omitempty"`
diff --git a/apis/pkg/meta/v1alpha1/zz_generated.meta.go b/apis/pkg/meta/v1alpha1/zz_generated.meta.go
index 396f28e8e..793a5c2fc 100644
--- a/apis/pkg/meta/v1alpha1/zz_generated.meta.go
+++ b/apis/pkg/meta/v1alpha1/zz_generated.meta.go
@@ -54,7 +54,7 @@ type Dependency struct {
 
 	// Package OCI reference of the dependency. Only used when apiVersion and
 	// kind are set.
-	// Musth be a fully qualified image name, including the registry,
+	// Must be a fully qualified image name, including the registry,
 	// repository, and tag. For example, "registry.example.com/repo/package:tag".
 	// +optional
 	Package *string `json:"package,omitempty"`
diff --git a/apis/pkg/meta/v1beta1/zz_generated.meta.go b/apis/pkg/meta/v1beta1/zz_generated.meta.go
index 593c871b3..3a4026e00 100644
--- a/apis/pkg/meta/v1beta1/zz_generated.meta.go
+++ b/apis/pkg/meta/v1beta1/zz_generated.meta.go
@@ -54,7 +54,7 @@ type Dependency struct {
 
 	// Package OCI reference of the dependency. Only used when apiVersion and
 	// kind are set.
-	// Musth be a fully qualified image name, including the registry,
+	// Must be a fully qualified image name, including the registry,
 	// repository, and tag. For example, "registry.example.com/repo/package:tag".
 	// +optional
 	Package *string `json:"package,omitempty"`
diff --git a/apis/pkg/v1/interfaces.go b/apis/pkg/v1/interfaces.go
index 9be8f6080..9c44ead6b 100644
--- a/apis/pkg/v1/interfaces.go
+++ b/apis/pkg/v1/interfaces.go
@@ -43,6 +43,15 @@ const (
 	// revisions, and can be used to select all provider revisions that belong
 	// to a particular family. It is not added to providers, only revisions.
 	LabelProviderFamily = "pkg.crossplane.io/provider-family"
+
+	// LabelProvider is used as the key for the provider name label.
+	LabelProvider = "pkg.crossplane.io/provider"
+
+	// LabelFunction is used as the key for the function name label.
+	LabelFunction = "pkg.crossplane.io/function"
+
+	// LabelRevision is used as the key for the package revision name label.
+	LabelRevision = "pkg.crossplane.io/revision"
 )
 
 var (
diff --git a/cluster/charts/crossplane/Chart.yaml b/cluster/charts/crossplane/Chart.yaml
index 7c47f84e2..cc6631329 100644
--- a/cluster/charts/crossplane/Chart.yaml
+++ b/cluster/charts/crossplane/Chart.yaml
@@ -7,7 +7,7 @@ icon: https://docs.crossplane.io/android-chrome-192x192.png
 home: https://crossplane.io
 maintainers:
   - name: Crossplane Maintainers
-    email: info@crossplane.io
+    email: crossplane-info@lists.cncf.io
 source:
 - https://github.com/crossplane/crossplane
 - https://github.com/crossplane/crossplane-runtime
diff --git a/cluster/charts/crossplane/README.md b/cluster/charts/crossplane/README.md
index 6393ce5ab..985602a13 100644
--- a/cluster/charts/crossplane/README.md
+++ b/cluster/charts/crossplane/README.md
@@ -99,6 +99,7 @@ and their default values.
 | `podSecurityContextCrossplane` | Add a custom `securityContext` to the Crossplane pod. | `{}` |
 | `podSecurityContextRBACManager` | Add a custom `securityContext` to the RBAC Manager pod. | `{}` |
 | `priorityClassName` | The PriorityClass name to apply to the Crossplane and RBAC Manager pods. | `""` |
+| `provider.defaultActivations` | Define entries for the default managed resource activation policy. If defined, a default MRAP will contain these activations. | `["*"]` |
 | `provider.packages` | A list of Provider packages to install. | `[]` |
 | `rbacManager.affinity` | Add `affinities` to the RBAC Manager pod deployment. | `{}` |
 | `rbacManager.args` | Add custom arguments to the RBAC Manager pod. | `[]` |
diff --git a/cluster/charts/crossplane/templates/deployment.yaml b/cluster/charts/crossplane/templates/deployment.yaml
index c66f09357..b527952b4 100644
--- a/cluster/charts/crossplane/templates/deployment.yaml
+++ b/cluster/charts/crossplane/templates/deployment.yaml
@@ -79,6 +79,10 @@ spec:
           - --function
           - "{{ $arg }}"
           {{- end }}
+          {{- range $arg := .Values.provider.defaultActivations }}
+          - --activation
+          - "{{ $arg }}"
+          {{- end }}
           resources:
             {{- toYaml .Values.resourcesCrossplane | nindent 12 }}
           {{- with .Values.securityContextCrossplane }}
@@ -147,7 +151,6 @@ spec:
         - {{ $arg }}
         {{- end }}
         imagePullPolicy: {{ .Values.image.pullPolicy }}
-        name: {{ .Chart.Name }}
         resources:
           {{- toYaml .Values.resourcesCrossplane | nindent 12 }}
         startupProbe:
diff --git a/cluster/charts/crossplane/values.yaml b/cluster/charts/crossplane/values.yaml
index e1632f99e..2f7afb915 100755
--- a/cluster/charts/crossplane/values.yaml
+++ b/cluster/charts/crossplane/values.yaml
@@ -57,6 +57,8 @@ args: []
 provider:
   # -- A list of Provider packages to install.
   packages: []
+  # -- Define entries for the default managed resource activation policy. If defined, a default MRAP will contain these activations.
+  defaultActivations: ["*"]
 
 configuration:
   # -- A list of Configuration packages to install.
diff --git a/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml b/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml
index 8d567b8f4..004336a2f 100644
--- a/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml
@@ -29,6 +29,9 @@ spec:
     - jsonPath: .metadata.creationTimestamp
       name: AGE
       type: date
+    deprecated: true
+    deprecationWarning: CompositeResourceDefinition v1 is deprecated and will be removed
+      in a future release; consider migrating to v2
     name: v1
     schema:
       openAPIV3Schema:
@@ -617,7 +620,7 @@ spec:
     - jsonPath: .metadata.creationTimestamp
       name: AGE
       type: date
-    name: v2alpha1
+    name: v2
     schema:
       openAPIV3Schema:
         description: |-
diff --git a/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml b/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml
index f1e40db83..b9efca736 100644
--- a/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml
@@ -164,6 +164,61 @@ spec:
                       type: object
                       x-kubernetes-embedded-resource: true
                       x-kubernetes-preserve-unknown-fields: true
+                    requirements:
+                      description: |-
+                        Requirements are resource requirements that will be satisfied before
+                        this pipeline step is called for the first time. This allows
+                        pre-populating required resources without requiring a function to
+                        request them first.
+                      properties:
+                        requiredResources:
+                          description: |-
+                            RequiredResources is a list of resources that must be fetched before
+                            this function is called.
+                          items:
+                            description: RequiredResourceSelector selects a required
+                              resource.
+                            properties:
+                              apiVersion:
+                                description: APIVersion of the required resource.
+                                type: string
+                              kind:
+                                description: Kind of the required resource.
+                                type: string
+                              matchLabels:
+                                additionalProperties:
+                                  type: string
+                                description: |-
+                                  MatchLabels specifies the set of labels to match for finding the
+                                  required resource. When specified, Name is ignored.
+                                type: object
+                              name:
+                                description: Name of the required resource.
+                                type: string
+                              namespace:
+                                description: Namespace of the required resource if
+                                  it is namespaced.
+                                type: string
+                              requirementName:
+                                description: |-
+                                  RequirementName is the unique name to identify this required resource
+                                  in the Required Resources map in the function request.
+                                type: string
+                            required:
+                            - apiVersion
+                            - kind
+                            - requirementName
+                            type: object
+                            x-kubernetes-validations:
+                            - message: Either name or matchLabels must be specified,
+                                but not both
+                              rule: (has(self.name) && !has(self.matchLabels)) ||
+                                (!has(self.name) && has(self.matchLabels))
+                          type: array
+                          x-kubernetes-list-map-keys:
+                          - requirementName
+                          x-kubernetes-list-type: map
+                      type: object
                     step:
                       description: Step name. Must be unique within its Pipeline.
                       type: string
diff --git a/cluster/crds/apiextensions.crossplane.io_compositions.yaml b/cluster/crds/apiextensions.crossplane.io_compositions.yaml
index 3e296ded5..ef264bd95 100644
--- a/cluster/crds/apiextensions.crossplane.io_compositions.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_compositions.yaml
@@ -160,6 +160,61 @@ spec:
                       type: object
                       x-kubernetes-embedded-resource: true
                       x-kubernetes-preserve-unknown-fields: true
+                    requirements:
+                      description: |-
+                        Requirements are resource requirements that will be satisfied before
+                        this pipeline step is called for the first time. This allows
+                        pre-populating required resources without requiring a function to
+                        request them first.
+                      properties:
+                        requiredResources:
+                          description: |-
+                            RequiredResources is a list of resources that must be fetched before
+                            this function is called.
+                          items:
+                            description: RequiredResourceSelector selects a required
+                              resource.
+                            properties:
+                              apiVersion:
+                                description: APIVersion of the required resource.
+                                type: string
+                              kind:
+                                description: Kind of the required resource.
+                                type: string
+                              matchLabels:
+                                additionalProperties:
+                                  type: string
+                                description: |-
+                                  MatchLabels specifies the set of labels to match for finding the
+                                  required resource. When specified, Name is ignored.
+                                type: object
+                              name:
+                                description: Name of the required resource.
+                                type: string
+                              namespace:
+                                description: Namespace of the required resource if
+                                  it is namespaced.
+                                type: string
+                              requirementName:
+                                description: |-
+                                  RequirementName is the unique name to identify this required resource
+                                  in the Required Resources map in the function request.
+                                type: string
+                            required:
+                            - apiVersion
+                            - kind
+                            - requirementName
+                            type: object
+                            x-kubernetes-validations:
+                            - message: Either name or matchLabels must be specified,
+                                but not both
+                              rule: (has(self.name) && !has(self.matchLabels)) ||
+                                (!has(self.name) && has(self.matchLabels))
+                          type: array
+                          x-kubernetes-list-map-keys:
+                          - requirementName
+                          x-kubernetes-list-type: map
+                      type: object
                     step:
                       description: Step name. Must be unique within its Pipeline.
                       type: string
diff --git a/cluster/crds/apiextensions.crossplane.io_managedresourceactivationpolicies.yaml b/cluster/crds/apiextensions.crossplane.io_managedresourceactivationpolicies.yaml
index fa6baee8c..400e4089d 100644
--- a/cluster/crds/apiextensions.crossplane.io_managedresourceactivationpolicies.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_managedresourceactivationpolicies.yaml
@@ -57,6 +57,7 @@ spec:
                   description: ActivationPolicy matches on MRD names with wildcard
                     prefix support.
                   type: string
+                minItems: 1
                 type: array
             required:
             - activate
diff --git a/cluster/crds/apiextensions.crossplane.io_managedresourcedefinitions.yaml b/cluster/crds/apiextensions.crossplane.io_managedresourcedefinitions.yaml
index 4a18aed6d..609b0af7f 100644
--- a/cluster/crds/apiextensions.crossplane.io_managedresourcedefinitions.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_managedresourcedefinitions.yaml
@@ -183,6 +183,9 @@ spec:
                   The custom resources are served under `/apis/<group>/...`.
                   Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
                 type: string
+                x-kubernetes-validations:
+                - message: Value is immutable
+                  rule: self == oldSelf
               names:
                 description: Names specify the resource and kind names for the custom
                   resource.
@@ -229,6 +232,13 @@ spec:
                 - kind
                 - plural
                 type: object
+                x-kubernetes-validations:
+                - message: Value is immutable
+                  rule: self == oldSelf
+                - message: Plural name must be lowercase
+                  rule: self.plural == self.plural.lowerAscii()
+                - message: Singular name must be lowercase
+                  rule: '!has(self.singular) || self.singular == self.singular.lowerAscii()'
               preserveUnknownFields:
                 description: |-
                   PreserveUnknownFields indicates that object fields which are not specified
@@ -238,14 +248,28 @@ spec:
                   See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning for details.
                 type: boolean
               scope:
+                default: Namespaced
                 description: |-
                   Scope indicates whether the defined custom resource is cluster- or namespace-scoped.
                   Allowed values are `Cluster` and `Namespaced`.
+                enum:
+                - Namespaced
+                - Cluster
                 type: string
+                x-kubernetes-validations:
+                - message: Value is immutable
+                  rule: self == oldSelf
               state:
+                default: Inactive
                 description: State toggles whether the underlying CRD is created or
                   not.
+                enum:
+                - Active
+                - Inactive
                 type: string
+                x-kubernetes-validations:
+                - message: state cannot be changed once it becomes Active
+                  rule: self == oldSelf || oldSelf != 'Active'
               versions:
                 description: |-
                   Versions is the list of all API versions of the defined custom resource.
@@ -308,7 +332,7 @@ spec:
                       x-kubernetes-list-type: atomic
                     deprecated:
                       description: |-
-                        Voldemort indicates this version of the custom resource API is deprecated.
+                        Deprecated indicates this version of the custom resource API is deprecated.
                         When set to true, API requests to this version receive a warning header in the server response.
                         Defaults to false.
                       type: boolean
@@ -428,7 +452,6 @@ spec:
             - group
             - names
             - scope
-            - state
             - versions
             type: object
           status:
diff --git a/cluster/crds/ops.crossplane.io_cronoperations.yaml b/cluster/crds/ops.crossplane.io_cronoperations.yaml
new file mode 100644
index 000000000..a7c331af8
--- /dev/null
+++ b/cluster/crds/ops.crossplane.io_cronoperations.yaml
@@ -0,0 +1,348 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.16.5
+  name: cronoperations.ops.crossplane.io
+spec:
+  group: ops.crossplane.io
+  names:
+    categories:
+    - crossplane
+    kind: CronOperation
+    listKind: CronOperationList
+    plural: cronoperations
+    shortNames:
+    - cronops
+    singular: cronoperation
+  scope: Cluster
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.schedule
+      name: SCHEDULE
+      type: string
+    - jsonPath: .status.conditions[?(@.type=='Synced')].status
+      name: SYNCED
+      type: string
+    - jsonPath: .status.conditions[?(@.type=='Scheduling')].status
+      name: SCHEDULING
+      type: string
+    - jsonPath: .status.lastScheduleTime
+      name: LAST SCHEDULE
+      type: date
+    - jsonPath: .status.lastSuccessfulTime
+      name: LAST SUCCESS
+      type: date
+    - jsonPath: .metadata.creationTimestamp
+      name: AGE
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: A CronOperation creates Operations on a cron schedule.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: CronOperationSpec specifies the desired state of a CronOperation.
+            properties:
+              concurrencyPolicy:
+                default: Allow
+                description: |-
+                  ConcurrencyPolicy specifies how to treat concurrent executions of an
+                  operation.
+                enum:
+                - Allow
+                - Forbid
+                - Replace
+                type: string
+              failedHistoryLimit:
+                default: 1
+                description: FailedHistoryLimit is the number of failed Operations
+                  to retain.
+                format: int32
+                type: integer
+              operationTemplate:
+                description: OperationTemplate is the template for the Operation to
+                  be created.
+                properties:
+                  metadata:
+                    description: Standard object metadata.
+                    type: object
+                  spec:
+                    description: Spec is the specification of the Operation to be
+                      created.
+                    properties:
+                      mode:
+                        default: Pipeline
+                        description: |-
+                          Mode controls what type or "mode" of operation will be used.
+
+                          "Pipeline" indicates that an Operation specifies a pipeline of
+                          functions, each of which is responsible for implementing its logic.
+                        enum:
+                        - Pipeline
+                        type: string
+                      pipeline:
+                        description: |-
+                          Pipeline is a list of operation function steps that will be used when
+                          this operation runs.
+                        items:
+                          description: A PipelineStep in an operation function pipeline.
+                          properties:
+                            credentials:
+                              description: Credentials are optional credentials that
+                                the operation function needs.
+                              items:
+                                description: |-
+                                  FunctionCredentials are optional credentials that a function
+                                  needs to run.
+                                properties:
+                                  name:
+                                    description: Name of this set of credentials.
+                                    type: string
+                                  secretRef:
+                                    description: |-
+                                      A SecretRef is a reference to a secret containing credentials that should
+                                      be supplied to the function.
+                                    properties:
+                                      name:
+                                        description: Name of the secret.
+                                        type: string
+                                      namespace:
+                                        description: Namespace of the secret.
+                                        type: string
+                                    required:
+                                    - name
+                                    - namespace
+                                    type: object
+                                  source:
+                                    description: Source of the function credentials.
+                                    enum:
+                                    - None
+                                    - Secret
+                                    type: string
+                                required:
+                                - name
+                                - source
+                                type: object
+                              type: array
+                              x-kubernetes-list-map-keys:
+                              - name
+                              x-kubernetes-list-type: map
+                            functionRef:
+                              description: |-
+                                FunctionRef is a reference to the function this step should
+                                execute.
+                              properties:
+                                name:
+                                  description: Name of the referenced function.
+                                  type: string
+                              required:
+                              - name
+                              type: object
+                            input:
+                              description: |-
+                                Input is an optional, arbitrary Kubernetes resource (i.e. a resource
+                                with an apiVersion and kind) that will be passed to the unction as
+                                the 'input' of its RunFunctionRequest.
+                              type: object
+                              x-kubernetes-embedded-resource: true
+                              x-kubernetes-preserve-unknown-fields: true
+                            requirements:
+                              description: |-
+                                Requirements are resource requirements that will be satisfied before
+                                this pipeline step is called for the first time. This allows
+                                pre-populating required resources without requiring a function to
+                                request them first.
+                              properties:
+                                requiredResources:
+                                  description: |-
+                                    RequiredResources that will be fetched before this pipeline step
+                                    is called for the first time.
+                                  items:
+                                    description: |-
+                                      RequiredResourceSelector selects resources that should be fetched before
+                                      a pipeline step runs.
+                                    properties:
+                                      apiVersion:
+                                        description: APIVersion of resources to select.
+                                        type: string
+                                      kind:
+                                        description: Kind of resources to select.
+                                        type: string
+                                      matchLabels:
+                                        additionalProperties:
+                                          type: string
+                                        description: |-
+                                          MatchLabels matches resources by label selector. Only one of Name or
+                                          MatchLabels may be specified.
+                                        type: object
+                                      name:
+                                        description: |-
+                                          Name matches a single resource by name. Only one of Name or
+                                          MatchLabels may be specified.
+                                        type: string
+                                      namespace:
+                                        description: Namespace to search for resources.
+                                          Optional for cluster-scoped resources.
+                                        type: string
+                                      requirementName:
+                                        description: |-
+                                          RequirementName uniquely identifies this group of resources.
+                                          This name will be used as the key in RunFunctionRequest.required_resources.
+                                        type: string
+                                    required:
+                                    - apiVersion
+                                    - kind
+                                    - requirementName
+                                    type: object
+                                    x-kubernetes-validations:
+                                    - message: Either name or matchLabels must be
+                                        specified, but not both
+                                      rule: (has(self.name) && !has(self.matchLabels))
+                                        || (!has(self.name) && has(self.matchLabels))
+                                  type: array
+                                  x-kubernetes-list-map-keys:
+                                  - requirementName
+                                  x-kubernetes-list-type: map
+                              type: object
+                            step:
+                              description: Step name. Must be unique within its Pipeline.
+                              type: string
+                          required:
+                          - functionRef
+                          - step
+                          type: object
+                        maxItems: 99
+                        minItems: 1
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - step
+                        x-kubernetes-list-type: map
+                      retryLimit:
+                        description: |-
+                          RetryLimit configures how many times the operation may fail. When the
+                          failure limit is exceeded, the operation will not be retried.
+                        format: int64
+                        type: integer
+                    required:
+                    - mode
+                    - pipeline
+                    type: object
+                required:
+                - spec
+                type: object
+              schedule:
+                description: Schedule is the cron schedule for the operation.
+                type: string
+              startingDeadlineSeconds:
+                description: |-
+                  StartingDeadlineSeconds is the deadline in seconds for starting the
+                  operation if it misses its scheduled time for any reason.
+                format: int64
+                type: integer
+              successfulHistoryLimit:
+                default: 3
+                description: SuccessfulHistoryLimit is the number of successful Operations
+                  to retain.
+                format: int32
+                type: integer
+            required:
+            - operationTemplate
+            - schedule
+            type: object
+          status:
+            description: CronOperationStatus represents the observed state of a CronOperation.
+            properties:
+              conditions:
+                description: Conditions of the resource.
+                items:
+                  description: A Condition that may apply to a resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        LastTransitionTime is the last time this condition transitioned from one
+                        status to another.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        A Message containing details about this condition's last transition from
+                        one status to another, if any.
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        ObservedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      type: integer
+                    reason:
+                      description: A Reason for this condition's last transition from
+                        one status to another.
+                      type: string
+                    status:
+                      description: Status of this condition; is it currently True,
+                        False, or Unknown?
+                      type: string
+                    type:
+                      description: |-
+                        Type of this condition. At most one of each condition type may apply to
+                        a resource at any point in time.
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+                x-kubernetes-list-map-keys:
+                - type
+                x-kubernetes-list-type: map
+              lastScheduleTime:
+                description: LastScheduleTime is the last time the CronOperation was
+                  scheduled.
+                format: date-time
+                type: string
+              lastSuccessfulTime:
+                description: |-
+                  LastSuccessfulTime is the last time the CronOperation was successfully
+                  completed.
+                format: date-time
+                type: string
+              runningOperationRefs:
+                description: RunningOperationRefs is a list of currently running Operations.
+                items:
+                  description: A RunningOperationRef is a reference to a running operation.
+                  properties:
+                    name:
+                      description: Name of the active operation.
+                      type: string
+                  required:
+                  - name
+                  type: object
+                type: array
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/cluster/crds/ops.crossplane.io_operations.yaml b/cluster/crds/ops.crossplane.io_operations.yaml
index 59b6be47f..f1ac96481 100644
--- a/cluster/crds/ops.crossplane.io_operations.yaml
+++ b/cluster/crds/ops.crossplane.io_operations.yaml
@@ -19,12 +19,15 @@ spec:
   scope: Cluster
   versions:
   - additionalPrinterColumns:
-    - jsonPath: .metadata.creationTimestamp
-      name: AGE
-      type: date
+    - jsonPath: .status.conditions[?(@.type=='Synced')].status
+      name: SYNCED
+      type: string
     - jsonPath: .status.conditions[?(@.type=='Succeeded')].status
       name: SUCCEEDED
       type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: AGE
+      type: date
     name: v1alpha1
     schema:
       openAPIV3Schema:
@@ -106,6 +109,9 @@ spec:
                         - source
                         type: object
                       type: array
+                      x-kubernetes-list-map-keys:
+                      - name
+                      x-kubernetes-list-type: map
                     functionRef:
                       description: |-
                         FunctionRef is a reference to the function this step should
@@ -125,6 +131,64 @@ spec:
                       type: object
                       x-kubernetes-embedded-resource: true
                       x-kubernetes-preserve-unknown-fields: true
+                    requirements:
+                      description: |-
+                        Requirements are resource requirements that will be satisfied before
+                        this pipeline step is called for the first time. This allows
+                        pre-populating required resources without requiring a function to
+                        request them first.
+                      properties:
+                        requiredResources:
+                          description: |-
+                            RequiredResources that will be fetched before this pipeline step
+                            is called for the first time.
+                          items:
+                            description: |-
+                              RequiredResourceSelector selects resources that should be fetched before
+                              a pipeline step runs.
+                            properties:
+                              apiVersion:
+                                description: APIVersion of resources to select.
+                                type: string
+                              kind:
+                                description: Kind of resources to select.
+                                type: string
+                              matchLabels:
+                                additionalProperties:
+                                  type: string
+                                description: |-
+                                  MatchLabels matches resources by label selector. Only one of Name or
+                                  MatchLabels may be specified.
+                                type: object
+                              name:
+                                description: |-
+                                  Name matches a single resource by name. Only one of Name or
+                                  MatchLabels may be specified.
+                                type: string
+                              namespace:
+                                description: Namespace to search for resources. Optional
+                                  for cluster-scoped resources.
+                                type: string
+                              requirementName:
+                                description: |-
+                                  RequirementName uniquely identifies this group of resources.
+                                  This name will be used as the key in RunFunctionRequest.required_resources.
+                                type: string
+                            required:
+                            - apiVersion
+                            - kind
+                            - requirementName
+                            type: object
+                            x-kubernetes-validations:
+                            - message: Either name or matchLabels must be specified,
+                                but not both
+                              rule: (has(self.name) && !has(self.matchLabels)) ||
+                                (!has(self.name) && has(self.matchLabels))
+                          type: array
+                          x-kubernetes-list-map-keys:
+                          - requirementName
+                          x-kubernetes-list-type: map
+                      type: object
                     step:
                       description: Step name. Must be unique within its Pipeline.
                       type: string
@@ -132,7 +196,12 @@ spec:
                   - functionRef
                   - step
                   type: object
+                maxItems: 99
+                minItems: 1
                 type: array
+                x-kubernetes-list-map-keys:
+                - step
+                x-kubernetes-list-type: map
               retryLimit:
                 description: |-
                   RetryLimit configures how many times the operation may fail. When the
diff --git a/cluster/crds/ops.crossplane.io_watchoperations.yaml b/cluster/crds/ops.crossplane.io_watchoperations.yaml
new file mode 100644
index 000000000..7a87c58c5
--- /dev/null
+++ b/cluster/crds/ops.crossplane.io_watchoperations.yaml
@@ -0,0 +1,380 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.16.5
+  name: watchoperations.ops.crossplane.io
+spec:
+  group: ops.crossplane.io
+  names:
+    categories:
+    - crossplane
+    kind: WatchOperation
+    listKind: WatchOperationList
+    plural: watchoperations
+    shortNames:
+    - watchops
+    singular: watchoperation
+  scope: Cluster
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.watch.kind
+      name: KIND
+      type: string
+    - jsonPath: .status.watchingResources
+      name: COUNT
+      type: integer
+    - jsonPath: .status.conditions[?(@.type=='Synced')].status
+      name: SYNCED
+      type: string
+    - jsonPath: .status.conditions[?(@.type=='Watching')].status
+      name: WATCHING
+      type: string
+    - jsonPath: .status.lastScheduleTime
+      name: LAST SCHEDULE
+      type: date
+    - jsonPath: .status.lastSuccessfulTime
+      name: LAST SUCCESS
+      type: date
+    - jsonPath: .metadata.creationTimestamp
+      name: AGE
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: A WatchOperation creates Operations when watched resources change.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: WatchOperationSpec specifies the desired state of a WatchOperation.
+            properties:
+              concurrencyPolicy:
+                default: Allow
+                description: |-
+                  ConcurrencyPolicy specifies how to treat concurrent executions of an
+                  operation.
+                enum:
+                - Allow
+                - Forbid
+                - Replace
+                type: string
+              failedHistoryLimit:
+                default: 1
+                description: FailedHistoryLimit is the number of failed Operations
+                  to retain.
+                format: int32
+                type: integer
+              operationTemplate:
+                description: OperationTemplate is the template for the Operation to
+                  be created.
+                properties:
+                  metadata:
+                    description: Standard object metadata.
+                    type: object
+                  spec:
+                    description: Spec is the specification of the Operation to be
+                      created.
+                    properties:
+                      mode:
+                        default: Pipeline
+                        description: |-
+                          Mode controls what type or "mode" of operation will be used.
+
+                          "Pipeline" indicates that an Operation specifies a pipeline of
+                          functions, each of which is responsible for implementing its logic.
+                        enum:
+                        - Pipeline
+                        type: string
+                      pipeline:
+                        description: |-
+                          Pipeline is a list of operation function steps that will be used when
+                          this operation runs.
+                        items:
+                          description: A PipelineStep in an operation function pipeline.
+                          properties:
+                            credentials:
+                              description: Credentials are optional credentials that
+                                the operation function needs.
+                              items:
+                                description: |-
+                                  FunctionCredentials are optional credentials that a function
+                                  needs to run.
+                                properties:
+                                  name:
+                                    description: Name of this set of credentials.
+                                    type: string
+                                  secretRef:
+                                    description: |-
+                                      A SecretRef is a reference to a secret containing credentials that should
+                                      be supplied to the function.
+                                    properties:
+                                      name:
+                                        description: Name of the secret.
+                                        type: string
+                                      namespace:
+                                        description: Namespace of the secret.
+                                        type: string
+                                    required:
+                                    - name
+                                    - namespace
+                                    type: object
+                                  source:
+                                    description: Source of the function credentials.
+                                    enum:
+                                    - None
+                                    - Secret
+                                    type: string
+                                required:
+                                - name
+                                - source
+                                type: object
+                              type: array
+                              x-kubernetes-list-map-keys:
+                              - name
+                              x-kubernetes-list-type: map
+                            functionRef:
+                              description: |-
+                                FunctionRef is a reference to the function this step should
+                                execute.
+                              properties:
+                                name:
+                                  description: Name of the referenced function.
+                                  type: string
+                              required:
+                              - name
+                              type: object
+                            input:
+                              description: |-
+                                Input is an optional, arbitrary Kubernetes resource (i.e. a resource
+                                with an apiVersion and kind) that will be passed to the unction as
+                                the 'input' of its RunFunctionRequest.
+                              type: object
+                              x-kubernetes-embedded-resource: true
+                              x-kubernetes-preserve-unknown-fields: true
+                            requirements:
+                              description: |-
+                                Requirements are resource requirements that will be satisfied before
+                                this pipeline step is called for the first time. This allows
+                                pre-populating required resources without requiring a function to
+                                request them first.
+                              properties:
+                                requiredResources:
+                                  description: |-
+                                    RequiredResources that will be fetched before this pipeline step
+                                    is called for the first time.
+                                  items:
+                                    description: |-
+                                      RequiredResourceSelector selects resources that should be fetched before
+                                      a pipeline step runs.
+                                    properties:
+                                      apiVersion:
+                                        description: APIVersion of resources to select.
+                                        type: string
+                                      kind:
+                                        description: Kind of resources to select.
+                                        type: string
+                                      matchLabels:
+                                        additionalProperties:
+                                          type: string
+                                        description: |-
+                                          MatchLabels matches resources by label selector. Only one of Name or
+                                          MatchLabels may be specified.
+                                        type: object
+                                      name:
+                                        description: |-
+                                          Name matches a single resource by name. Only one of Name or
+                                          MatchLabels may be specified.
+                                        type: string
+                                      namespace:
+                                        description: Namespace to search for resources.
+                                          Optional for cluster-scoped resources.
+                                        type: string
+                                      requirementName:
+                                        description: |-
+                                          RequirementName uniquely identifies this group of resources.
+                                          This name will be used as the key in RunFunctionRequest.required_resources.
+                                        type: string
+                                    required:
+                                    - apiVersion
+                                    - kind
+                                    - requirementName
+                                    type: object
+                                    x-kubernetes-validations:
+                                    - message: Either name or matchLabels must be
+                                        specified, but not both
+                                      rule: (has(self.name) && !has(self.matchLabels))
+                                        || (!has(self.name) && has(self.matchLabels))
+                                  type: array
+                                  x-kubernetes-list-map-keys:
+                                  - requirementName
+                                  x-kubernetes-list-type: map
+                              type: object
+                            step:
+                              description: Step name. Must be unique within its Pipeline.
+                              type: string
+                          required:
+                          - functionRef
+                          - step
+                          type: object
+                        maxItems: 99
+                        minItems: 1
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - step
+                        x-kubernetes-list-type: map
+                      retryLimit:
+                        description: |-
+                          RetryLimit configures how many times the operation may fail. When the
+                          failure limit is exceeded, the operation will not be retried.
+                        format: int64
+                        type: integer
+                    required:
+                    - mode
+                    - pipeline
+                    type: object
+                required:
+                - spec
+                type: object
+              successfulHistoryLimit:
+                default: 3
+                description: SuccessfulHistoryLimit is the number of successful Operations
+                  to retain.
+                format: int32
+                type: integer
+              watch:
+                description: Watch specifies the resource to watch.
+                properties:
+                  apiVersion:
+                    description: APIVersion of the resource to watch.
+                    type: string
+                    x-kubernetes-validations:
+                    - message: apiVersion is immutable
+                      rule: self == oldSelf
+                  kind:
+                    description: Kind of the resource to watch.
+                    type: string
+                    x-kubernetes-validations:
+                    - message: kind is immutable
+                      rule: self == oldSelf
+                  matchLabels:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      MatchLabels selects resources by label. If empty, all resources of the
+                      specified kind are watched.
+                    type: object
+                  namespace:
+                    description: |-
+                      Namespace selects resources in a specific namespace. If empty, all
+                      namespaces are watched. Only applicable for namespaced resources.
+                    type: string
+                required:
+                - apiVersion
+                - kind
+                type: object
+            required:
+            - operationTemplate
+            - watch
+            type: object
+          status:
+            description: WatchOperationStatus represents the observed state of a WatchOperation.
+            properties:
+              conditions:
+                description: Conditions of the resource.
+                items:
+                  description: A Condition that may apply to a resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        LastTransitionTime is the last time this condition transitioned from one
+                        status to another.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        A Message containing details about this condition's last transition from
+                        one status to another, if any.
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        ObservedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      type: integer
+                    reason:
+                      description: A Reason for this condition's last transition from
+                        one status to another.
+                      type: string
+                    status:
+                      description: Status of this condition; is it currently True,
+                        False, or Unknown?
+                      type: string
+                    type:
+                      description: |-
+                        Type of this condition. At most one of each condition type may apply to
+                        a resource at any point in time.
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+                x-kubernetes-list-map-keys:
+                - type
+                x-kubernetes-list-type: map
+              lastScheduleTime:
+                description: |-
+                  LastScheduleTime is the last time the WatchOperation created an
+                  Operation.
+                format: date-time
+                type: string
+              lastSuccessfulTime:
+                description: |-
+                  LastSuccessfulTime is the last time the WatchOperation successfully
+                  completed an Operation.
+                format: date-time
+                type: string
+              runningOperationRefs:
+                description: RunningOperationRefs is a list of currently running Operations.
+                items:
+                  description: A RunningOperationRef is a reference to a running operation.
+                  properties:
+                    name:
+                      description: Name of the active operation.
+                      type: string
+                  required:
+                  - name
+                  type: object
+                type: array
+              watchingResources:
+                description: |-
+                  WatchingResources is the number of resources this WatchOperation is
+                  currently watching.
+                format: int64
+                type: integer
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/cluster/meta/meta.pkg.crossplane.io_configurations.yaml b/cluster/meta/meta.pkg.crossplane.io_configurations.yaml
index 9dec57f83..e55837155 100644
--- a/cluster/meta/meta.pkg.crossplane.io_configurations.yaml
+++ b/cluster/meta/meta.pkg.crossplane.io_configurations.yaml
@@ -90,7 +90,7 @@ spec:
                       description: |-
                         Package OCI reference of the dependency. Only used when apiVersion and
                         kind are set.
-                        Musth be a fully qualified image name, including the registry,
+                        Must be a fully qualified image name, including the registry,
                         repository, and tag. For example, "registry.example.com/repo/package:tag".
                       type: string
                     provider:
@@ -189,7 +189,7 @@ spec:
                       description: |-
                         Package OCI reference of the dependency. Only used when apiVersion and
                         kind are set.
-                        Musth be a fully qualified image name, including the registry,
+                        Must be a fully qualified image name, including the registry,
                         repository, and tag. For example, "registry.example.com/repo/package:tag".
                       type: string
                     provider:
diff --git a/cluster/meta/meta.pkg.crossplane.io_functions.yaml b/cluster/meta/meta.pkg.crossplane.io_functions.yaml
index 79df94bbe..c34d218eb 100644
--- a/cluster/meta/meta.pkg.crossplane.io_functions.yaml
+++ b/cluster/meta/meta.pkg.crossplane.io_functions.yaml
@@ -89,7 +89,7 @@ spec:
                       description: |-
                         Package OCI reference of the dependency. Only used when apiVersion and
                         kind are set.
-                        Musth be a fully qualified image name, including the registry,
+                        Must be a fully qualified image name, including the registry,
                         repository, and tag. For example, "registry.example.com/repo/package:tag".
                       type: string
                     provider:
@@ -187,7 +187,7 @@ spec:
                       description: |-
                         Package OCI reference of the dependency. Only used when apiVersion and
                         kind are set.
-                        Musth be a fully qualified image name, including the registry,
+                        Must be a fully qualified image name, including the registry,
                         repository, and tag. For example, "registry.example.com/repo/package:tag".
                       type: string
                     provider:
diff --git a/cluster/meta/meta.pkg.crossplane.io_providers.yaml b/cluster/meta/meta.pkg.crossplane.io_providers.yaml
index d469563c4..77f8d5c4b 100644
--- a/cluster/meta/meta.pkg.crossplane.io_providers.yaml
+++ b/cluster/meta/meta.pkg.crossplane.io_providers.yaml
@@ -89,7 +89,7 @@ spec:
                       description: |-
                         Package OCI reference of the dependency. Only used when apiVersion and
                         kind are set.
-                        Musth be a fully qualified image name, including the registry,
+                        Must be a fully qualified image name, including the registry,
                         repository, and tag. For example, "registry.example.com/repo/package:tag".
                       type: string
                     provider:
@@ -187,7 +187,7 @@ spec:
                       description: |-
                         Package OCI reference of the dependency. Only used when apiVersion and
                         kind are set.
-                        Musth be a fully qualified image name, including the registry,
+                        Must be a fully qualified image name, including the registry,
                         repository, and tag. For example, "registry.example.com/repo/package:tag".
                       type: string
                     provider:
diff --git a/cmd/crank/beta/trace/internal/printer/default.go b/cmd/crank/beta/trace/internal/printer/default.go
index 4b1a47292..ec2dbdaa3 100644
--- a/cmd/crank/beta/trace/internal/printer/default.go
+++ b/cmd/crank/beta/trace/internal/printer/default.go
@@ -33,7 +33,7 @@ import (
 	pkgv1 "github.com/crossplane/crossplane/apis/pkg/v1"
 	"github.com/crossplane/crossplane/cmd/crank/beta/trace/internal/resource"
 	"github.com/crossplane/crossplane/cmd/crank/beta/trace/internal/resource/xpkg"
-	"github.com/crossplane/crossplane/internal/controller/apiextensions/composite"
+	"github.com/crossplane/crossplane/internal/xcrd"
 )
 
 const (
@@ -269,7 +269,7 @@ func getResourceStatus(r *resource.Resource, name string, wide bool) fmt.Stringe
 	return &defaultPrinterRow{
 		wide:         wide,
 		name:         name,
-		resourceName: r.Unstructured.GetAnnotations()[composite.AnnotationKeyCompositionResourceName],
+		resourceName: r.Unstructured.GetAnnotations()[xcrd.AnnotationKeyCompositionResourceName],
 		ready:        mapEmptyStatusToDash(readyCond.Status),
 		synced:       mapEmptyStatusToDash(syncedCond.Status),
 		status:       status,
diff --git a/cmd/crank/beta/validate/validate.go b/cmd/crank/beta/validate/validate.go
index ac3c3c8db..8b542b8b5 100644
--- a/cmd/crank/beta/validate/validate.go
+++ b/cmd/crank/beta/validate/validate.go
@@ -34,7 +34,7 @@ import (
 
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 
-	"github.com/crossplane/crossplane/internal/controller/apiextensions/composite"
+	"github.com/crossplane/crossplane/internal/xcrd"
 )
 
 const (
@@ -182,7 +182,7 @@ func getResourceName(r *unstructured.Unstructured) string {
 	}
 
 	// fallback to composition resource name
-	return r.GetAnnotations()[composite.AnnotationKeyCompositionResourceName]
+	return r.GetAnnotations()[xcrd.AnnotationKeyCompositionResourceName]
 }
 
 // applyDefaults applies default values from the CRD schema to the unstructured resource.
diff --git a/cmd/crank/xpkg/batch.go b/cmd/crank/xpkg/batch.go
new file mode 100644
index 000000000..6e5f8c8bc
--- /dev/null
+++ b/cmd/crank/xpkg/batch.go
@@ -0,0 +1,563 @@
+/*
+Copyright 2025 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 xpkg
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"text/template"
+
+	"github.com/google/go-containerregistry/pkg/name"
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/daemon"
+	"github.com/google/go-containerregistry/pkg/v1/mutate"
+	"github.com/google/go-containerregistry/pkg/v1/tarball"
+	"github.com/spf13/afero"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/parser"
+
+	"github.com/crossplane/crossplane/internal/xpkg"
+	"github.com/crossplane/crossplane/internal/xpkg/parser/examples"
+	"github.com/crossplane/crossplane/internal/xpkg/parser/yaml"
+)
+
+const (
+	errInvalidTemplate    = "smaller provider metadata template is not valid"
+	errMetadataBackend    = "failed to initialize the package metadata parser backend"
+	errCRDBackend         = "failed to initialize the CRD parser backend"
+	errTemplateFmt        = "failed to execute the provider metadata template using: %v"
+	errInvalidPlatformFmt = "failed to parse platform name. Expected syntax is <OS>_<arch>: %s"
+	errBuildPackageFmt    = "failed to build smaller provider package: %s"
+	errGetConfigFmt       = "failed to get config file from %s image for service %q"
+	errMutateConfigFmt    = "failed to mutate config file from %s image for service %q"
+	errGetLayersFmt       = "failed to get layers from %s image for service %q"
+	errGetBaseLayersFmt   = "failed to get base layers from %s image for service %q"
+	errGetDigestFmt       = "failed to get layer's digest from %s image for service %q"
+	errAppendLayersFmt    = "failed to append layers to %s image for service %q"
+	errReadProviderBinFmt = "failed to read %q provider binary for %s platform from path: %s"
+	errNewLayerFmt        = "failed to initialize a new image layer for %s platform for service %q"
+	errAddLayerFmt        = "failed to add the smaller provider binary layer for %s platform for service %q"
+	errPushPackageFmt     = "failed to push smaller provider package: %s"
+	errProcessFmt         = "\nfailed to process smaller provider package for %q"
+	errOutputAbsFmt       = "failed to get the absolute path for the package archive to store: %s/%s/%s"
+	errOpenPackageFmt     = "failed to open package file for writing: %s"
+	errWritePackageFmt    = "failed to store package archive in: %s"
+	errBatch              = "processing of at least one smaller provider has failed"
+)
+
+const (
+	wildcard  = "*"
+	tagLatest = "latest"
+)
+
+// AfterApply constructs and binds context to any subcommands
+// that have Run() methods that receive it.
+func (c *batchCmd) AfterApply() error {
+	c.fs = afero.NewOsFs()
+	return nil
+}
+
+// batchCmd builds and pushes a family of Crossplane provider packages.
+type batchCmd struct {
+	fs afero.Fs
+
+	FamilyBaseImage        string   `help:"Family image used as the base for the smaller provider packages."                                                                                                                                       required:""`
+	ProviderName           string   `help:"Provider name, such as provider-aws to be used while formatting smaller provider package repositories."                                                                                                 required:""`
+	FamilyPackageURLFormat string   `help:"Family package URL format to be used for the smaller provider packages. Must be a valid OCI image URL with the format specifier \"%s\", which will be substituted with <provider name>-<service name>." required:""`
+	SmallerProviders       []string `default:"monolith"                                                                                                                                                                                            help:"Smaller provider names to build and push, such as ec2, eks or config."`
+	Concurrency            uint     `default:"0"                                                                                                                                                                                                   help:"Maximum number of packages to process concurrently. Setting it to 0 puts no limit on the concurrency, i.e., all packages are processed in parallel."`
+	PushRetry              uint     `default:"3"                                                                                                                                                                                                   help:"Number of retries when pushing a provider package fails."`
+
+	Platform        []string `default:"linux_amd64,linux_arm64"                                                                                                                help:"Platforms to build the packages for. Each platform should use the <OS>_<arch> syntax. An example is: linux_arm64."`
+	ProviderBinRoot string   `help:"Provider binary paths root. Smaller provider binaries should reside under the platform directories in this folder."                        short:"p"                                                                                                                type:"existingdir"`
+	OutputDir       string   `help:"Path of the package output directory."                                                                                                     optional:""                                                                                                              short:"o"`
+	StorePackages   []string `help:"Smaller provider names whose provider package should be stored under the package output directory specified with the --output-dir option." optional:""`
+
+	PackageMetadataTemplate string            `default:"./package/crossplane.yaml.tmpl"                                                   help:"Smaller provider metadata template. The template variables {{ .Service }} and {{ .Name }} will be substituted when the template is executed among with the supplied template variable substitutions." type:"path"`
+	TemplateVar             map[string]string `help:"Smaller provider metadata template variables to be used for the specified template."`
+
+	ExamplesGroupOverride map[string]string `help:"Overrides for the location of the example manifests folder of a smaller provider." optional:""`
+	CRDGroupOverride      map[string]string `help:"Overrides for the locations of the CRD folders of the smaller providers."          optional:""`
+	PackageRepoOverride   map[string]string `help:"Overrides for the package repository names of the smaller providers."              optional:""`
+
+	ExamplesRoot string   `default:"./examples"                                        help:"Path to package examples directory."                                                               short:"e"   type:"path"`
+	CRDRoot      string   `default:"./package/crds"                                    help:"Path to package CRDs directory."                                                                   type:"path"`
+	Ignore       []string `help:"Paths to exclude from the smaller provider packages."`
+	BuildOnly    bool     `default:"false"                                             help:"Only build the smaller provider packages and do not attempt to push them to a package repository."`
+
+	ProviderNameSuffixForPush string `env:"PROVIDER_NAME_SUFFIX_FOR_PUSH" help:"Suffix for provider name during pushing the packages. This suffix is added to the end of the provider name. If there is a service name for the corresponded provider, then the suffix will be added to the base provider name and the service-scoped name will be after this suffix.  Examples: provider-family-aws-suffix, provider-aws-suffix-s3" optional:""`
+}
+
+// Run executes the batch command.
+func (c *batchCmd) Run(logger logging.Logger) error {
+	ctx := context.Background()
+	baseImgMap, err := makeBaseImgMap(ctx, c.Platform, c.FamilyBaseImage)
+	if err != nil {
+		return err
+	}
+
+	chErr := make(chan error, len(c.SmallerProviders))
+	defer close(chErr)
+	concurrency := make(chan struct{}, c.Concurrency)
+	defer close(concurrency)
+	for range c.Concurrency {
+		concurrency <- struct{}{}
+	}
+	for _, s := range c.SmallerProviders {
+		go func() {
+			// if concurrency is limited
+			if c.Concurrency != 0 {
+				<-concurrency
+				defer func() {
+					concurrency <- struct{}{}
+				}()
+			}
+			err := c.processService(logger, baseImgMap, s)
+			if err != nil {
+				logger.Info("Publishing of smaller provider package has failed for service", "service", s, "error", err)
+			}
+			chErr <- errors.WithMessagef(err, errProcessFmt, s)
+		}()
+	}
+	var result error
+	for range c.SmallerProviders {
+		err := <-chErr
+		switch {
+		case result == nil:
+			result = err
+		case err != nil:
+			result = errors.Wrap(result, err.Error())
+		}
+	}
+	return errors.WithMessage(result, errBatch)
+}
+
+// makeBaseImgMap processes the given platforms to return a map of platforms to
+// base images to use for those platforms.
+func makeBaseImgMap(ctx context.Context, platforms []string, familyBaseImage string) (map[string]v1.Image, error) {
+	baseImgMap := make(map[string]v1.Image, len(platforms))
+	for _, p := range platforms {
+		tokens := strings.Split(p, "_")
+		if len(tokens) != 2 {
+			return nil, errors.Errorf(errInvalidPlatformFmt, p)
+		}
+		ref, err := name.ParseReference(fmt.Sprintf("%s-%s", familyBaseImage, tokens[1]))
+		if err != nil {
+			return nil, err
+		}
+		img, err := daemon.Image(ref, daemon.WithContext(ctx))
+		if err != nil {
+			return nil, err
+		}
+		baseImgMap[p] = img // assumes correct OS
+	}
+
+	return baseImgMap, nil
+}
+
+// processService builds and pushes the smaller provider package
+// associated with the specified service `s` and for the specified platforms.
+// Each smaller provider package share a common (platform specific) base
+// image specified in the `baseImgMap` keyed by the platform name
+// (e.g., linux_arm64). The addendum layers (i.e., the layers
+// added by xpkg push on top of the base image) are shared across platforms,
+// and thus is computed only once. `processService` also adds
+// the smaller provider controller binary (which is platform specific) on top
+// of the addendum layers and then pushes the built multi-arch package
+// (if `len(c.Platforms) > 1`) to the specified package repository.
+func (c *batchCmd) processService(logger logging.Logger, baseImgMap map[string]v1.Image, s string) error {
+	imgs := make([]packageImage, 0, len(c.Platform))
+	// image layers added on top of the base image by xpkg push to be reused
+	// across the platforms so that they are computed only once.
+	var addendumLayers []v1.Layer
+	// labels in the image configuration associated with these addendum layers.
+	var labels [][2]string
+	for _, p := range c.Platform {
+		var img v1.Image
+		var err error
+		switch {
+		// if the addendum layers have already been computed,
+		// use them instead of recomputing.
+		case len(addendumLayers) > 0:
+			img = baseImgMap[p]
+			img, err = c.appendAddendumLayers(img, addendumLayers, labels, p, s)
+			if err != nil {
+				return err
+			}
+		// then we need to compute the provider metadata "base" layer and the extensions layer.
+		default:
+			img, err = c.buildImage(baseImgMap, p, s)
+			if err != nil {
+				return err
+			}
+			// calculate addendum layers to reuse
+			addendumLayers, labels, err = getAddendumLayers(baseImgMap[p], img, p, s)
+			if err != nil {
+				return err
+			}
+		}
+		imgs = append(imgs, packageImage{Image: img, Path: fmt.Sprintf("%s-%s", s, p)})
+	}
+	if err := c.storePackage(logger, s, imgs); err != nil {
+		return err
+	}
+	if c.BuildOnly {
+		return nil
+	}
+	// now try to push the package with the specified retry configuration.
+	return c.pushWithRetry(logger, imgs, s)
+}
+
+// Optionally stores the provider package under the configured directory,
+// if the service name exists in the c.StorePackage slice.
+func (c *batchCmd) storePackage(logger logging.Logger, s string, imgs []packageImage) error {
+	found := false
+	for _, pkg := range c.StorePackages {
+		if pkg == s {
+			found = true
+			break
+		}
+	}
+	if !found {
+		return nil
+	}
+	for i, p := range c.Platform {
+		if err := c.writePackage(logger, s, p, imgs[i].Image); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// writePackage writes the given image as an xpkg file to the output
+// directory, within a sub directory for each platform and each package file name
+// with format: {provider}-{service}-{version}.xpkg.
+func (c *batchCmd) writePackage(logger logging.Logger, service, platform string, img v1.Image) error {
+	fName := fmt.Sprintf("%s-%s-%s.xpkg", c.ProviderName, service, c.getPackageVersion())
+	pkgPath, err := filepath.Abs(filepath.Join(c.OutputDir, platform, fName))
+	if err != nil {
+		return errors.Wrapf(err, errOutputAbsFmt, c.OutputDir, platform, fName)
+	}
+	pkg, err := c.fs.Create(pkgPath)
+	if err != nil {
+		return errors.Wrapf(err, errOpenPackageFmt, pkgPath)
+	}
+	defer func() { _ = pkg.Close() }()
+	if err := tarball.Write(nil, img, pkg); err != nil {
+		return errors.Wrapf(err, errWritePackageFmt, pkgPath)
+	}
+	logger.Info(fmt.Sprintf("xpkg for service %q saved to %s", service, pkgPath))
+	return nil
+}
+
+// pushWithRetry attempts to push the given package images up to the configured
+// retry count. If all retries fail then an error is returned.
+func (c *batchCmd) pushWithRetry(logger logging.Logger, imgs []packageImage, s string) error {
+	t := c.getPackageURL(s)
+	tries := c.PushRetry + 1
+	retryMsg := ""
+	for i := range tries {
+		logger.Info(fmt.Sprintf("Pushing xpkg to %s.%s", t, retryMsg))
+		err := pushImages(logger, imgs, t)
+		if err == nil {
+			break
+		}
+		if i == tries-1 { // no more retries
+			logger.Info(fmt.Sprintf("Failed to push xpkg to %s. Total number of attempts: %d. Last error: %s", t, tries, err.Error()))
+			return errors.Wrapf(err, errPushPackageFmt, s)
+		}
+
+		logger.Info(fmt.Sprintf("Failed to push xpkg to %s. Will retry...: %v", t, err))
+		retryMsg = fmt.Sprintf(" Retry count: %d", i+1)
+	}
+	return nil
+}
+
+func (c *batchCmd) getPackageVersion() string {
+	tokens := strings.Split(c.FamilyPackageURLFormat, ":")
+	if len(tokens) < 2 {
+		return tagLatest
+	}
+	return tokens[len(tokens)-1]
+}
+
+func (c *batchCmd) getPackageRepo(s string) string {
+	repo := c.PackageRepoOverride[s]
+	if repo == "" {
+		repo = fmt.Sprintf("%s-%s", c.ProviderName, s)
+	}
+	return repo
+}
+
+func (c *batchCmd) getPackageRepoWithSuffix(s string) string {
+	if v, ok := c.PackageRepoOverride[s]; ok {
+		return fmt.Sprintf("%s-%s", v, c.ProviderNameSuffixForPush)
+	}
+	return fmt.Sprintf("%s-%s-%s", c.ProviderName, c.ProviderNameSuffixForPush, s)
+}
+
+func (c *batchCmd) getPackageURL(s string) string {
+	if c.ProviderNameSuffixForPush != "" {
+		return fmt.Sprintf(c.FamilyPackageURLFormat, c.getPackageRepoWithSuffix(s))
+	}
+	return fmt.Sprintf(c.FamilyPackageURLFormat, c.getPackageRepo(s))
+}
+
+// getAddendumLayers returns the diff layers between the specified
+// `baseImg` and the specified `img`. For each of these addendum layers,
+// it also returns labels associated with that layer
+// in the image configuration as a slice of (key, value) pairs.
+func getAddendumLayers(baseImg, img v1.Image, platform, service string) (addendumLayers []v1.Layer, layerLabels [][2]string, err error) {
+	baseLayers, err := baseImg.Layers()
+	if err != nil {
+		return nil, nil, errors.Wrapf(err, errGetBaseLayersFmt, platform, service)
+	}
+	layers, err := img.Layers()
+	if err != nil {
+		return nil, nil, errors.Wrapf(err, errGetLayersFmt, platform, service)
+	}
+	addendumLayers = layers[len(baseLayers) : len(layers)-1]
+	// get associated labels from image config
+	cfg, err := img.ConfigFile()
+	if err != nil {
+		return nil, nil, errors.Wrapf(err, errGetConfigFmt, platform, service)
+	}
+	layerLabels = make([][2]string, 0, len(addendumLayers))
+	for _, l := range addendumLayers {
+		d, err := l.Digest()
+		if err != nil {
+			return nil, nil, errors.Wrapf(err, errGetDigestFmt, platform, service)
+		}
+		label := ""
+		key := xpkg.Label(d.String())
+		for k, v := range cfg.Config.Labels {
+			if key == k {
+				label = v
+				break
+			}
+		}
+		layerLabels = append(layerLabels, [2]string{key, label})
+	}
+	return addendumLayers, layerLabels, nil
+}
+
+// appendAddendumLayers appends the given addendum layers to the image and
+// labels to the image config, allowing reuse of the addendum layers and only
+// calculating them once.
+func (c *batchCmd) appendAddendumLayers(img v1.Image, addendumLayers []v1.Layer, labels [][2]string, p, s string) (v1.Image, error) {
+	var err error
+	for _, l := range addendumLayers {
+		img, err = mutate.AppendLayers(img, l)
+		if err != nil {
+			return nil, errors.Wrapf(err, errAppendLayersFmt, p, s)
+		}
+	}
+	// add any already computed addendum layer labels
+	// into the image configuration.
+	cfg, err := img.ConfigFile()
+	if err != nil {
+		return nil, errors.Wrapf(err, errGetConfigFmt, p, s)
+	}
+	if cfg.Config.Labels == nil {
+		cfg.Config.Labels = make(map[string]string, len(labels))
+	}
+	for _, kv := range labels {
+		if kv[1] == "" {
+			continue
+		}
+		cfg.Config.Labels[kv[0]] = kv[1]
+	}
+	img, err = mutate.Config(img, cfg.Config)
+	if err != nil {
+		return nil, errors.Wrapf(err, errMutateConfigFmt, p, s)
+	}
+	// add the smaller provider controller binary as a layer.
+	return c.addProviderBinaryLayer(img, p, s)
+}
+
+func (c *batchCmd) buildImage(baseImgMap map[string]v1.Image, p, s string) (v1.Image, error) {
+	builder, err := c.getBuilder(s)
+	if err != nil {
+		return nil, err
+	}
+	img, _, err := builder.Build(context.Background(), xpkg.WithBase(baseImgMap[p]))
+	if err != nil {
+		return nil, errors.Wrapf(err, errBuildPackageFmt, s)
+	}
+	return c.addProviderBinaryLayer(img, p, s)
+}
+
+// addProviderBinaryLayer adds the platform specific provider executable as the
+// final layer to the given image.
+func (c *batchCmd) addProviderBinaryLayer(img v1.Image, p, s string) (v1.Image, error) {
+	configFile, err := img.ConfigFile()
+	if err != nil {
+		return nil, errors.Wrapf(err, errGetConfigFmt, p, s)
+	}
+	binPath := filepath.Join(c.ProviderBinRoot, p, s)
+	buff, err := os.ReadFile(filepath.Clean(binPath))
+	if err != nil {
+		return nil, errors.Wrapf(err, errReadProviderBinFmt, s, p, binPath)
+	}
+	l, err := xpkg.Layer(bytes.NewBuffer(buff), "/usr/local/bin/provider", "", int64(len(buff)), 0o755, &configFile.Config)
+	if err != nil {
+		return nil, errors.Wrapf(err, errNewLayerFmt, p, s)
+	}
+	img, err = mutate.AppendLayers(img, l)
+	return img, errors.Wrapf(err, errAddLayerFmt, p, s)
+}
+
+func (c *batchCmd) getExamplesGroup(service string) string {
+	p := c.ExamplesGroupOverride[service]
+	switch p {
+	case wildcard:
+		p = ""
+	case "":
+		p = service
+	}
+	return filepath.Join(c.ExamplesRoot, p)
+}
+
+// getBuilder initializes an xpkg builder for the given service that uses our
+// batch parser backend to parse all the content for the package.
+func (c *batchCmd) getBuilder(service string) (*xpkg.Builder, error) {
+	ex, err := filepath.Abs(c.getExamplesGroup(service))
+	if err != nil {
+		return nil, err
+	}
+
+	pp, err := yaml.New()
+	if err != nil {
+		return nil, err
+	}
+
+	packageMetadata, err := c.getPackageMetadata(service)
+	if err != nil {
+		return nil, err
+	}
+
+	return xpkg.New(
+		&batchParserBackend{
+			packageMetadata: packageMetadata,
+			service:         service,
+			fs:              c.fs,
+			options: []parser.BackendOption{
+				parser.FsDir(c.CRDRoot),
+				parser.FsFilters(
+					append(
+						buildFilters(c.CRDRoot, c.Ignore),
+						xpkg.SkipContains(c.ExamplesRoot),
+						func(_ string, info os.FileInfo) (bool, error) {
+							return !strings.HasPrefix(info.Name(), c.getCRDPrefix(service)), nil
+						})...),
+			},
+		},
+		parser.NewFsBackend(
+			c.fs,
+			parser.FsDir(ex),
+			parser.FsFilters(
+				buildFilters(ex, c.Ignore)...),
+		),
+		pp,
+		examples.New(),
+	), nil
+}
+
+func (c *batchCmd) getCRDPrefix(service string) string {
+	o := c.CRDGroupOverride[service]
+	if o == wildcard {
+		return ""
+	}
+	if o == "" {
+		o = service
+	}
+	return o + "."
+}
+
+// getPackageMetadata uses the provided package metadata template and does
+// variable substitution to create the final package metadata file (i.e.,
+// crossplane.yaml).
+func (c *batchCmd) getPackageMetadata(service string) (string, error) {
+	tmpl, err := template.New(filepath.Base(c.PackageMetadataTemplate)).ParseFiles(c.PackageMetadataTemplate)
+	if err != nil {
+		return "", errors.Wrap(err, errInvalidTemplate)
+	}
+
+	// prepare template var substitutions
+	data := make(map[string]string, len(c.TemplateVar)+2)
+	data["Service"] = service
+	data["Name"] = c.getPackageRepo(service)
+	// copy substitutions passed from the command-line
+	for k, v := range c.TemplateVar {
+		data[k] = v
+	}
+
+	buff := &bytes.Buffer{}
+	err = tmpl.Execute(buff, data)
+	if err != nil {
+		return "", errors.Wrapf(err, errTemplateFmt, data)
+	}
+	return buff.String(), nil
+}
+
+type batchParserBackend struct {
+	packageMetadata string
+	service         string
+	options         []parser.BackendOption
+
+	fs afero.Fs
+}
+
+func (b *batchParserBackend) Init(ctx context.Context, opts ...parser.BackendOption) (io.ReadCloser, error) {
+	rcMetadata, err := parser.NewEchoBackend(b.packageMetadata).Init(ctx, opts...)
+	if err != nil {
+		return nil, errors.Wrap(err, errMetadataBackend)
+	}
+	rcCRD, err := parser.NewFsBackend(b.fs, b.options...).Init(ctx, opts...)
+	if err != nil {
+		return nil, errors.Wrap(err, errCRDBackend)
+	}
+	return &batchReadCloser{
+		metadataReadCloser: rcMetadata,
+		crdReadCloser:      rcCRD,
+	}, nil
+}
+
+type batchReadCloser struct {
+	metadataReadCloser io.ReadCloser
+	crdReadCloser      io.ReadCloser
+	metadataRead       bool
+}
+
+func (b *batchReadCloser) Read(p []byte) (n int, err error) {
+	if !b.metadataRead {
+		b.metadataRead = true
+		return b.metadataReadCloser.Read(p)
+	}
+	return b.crdReadCloser.Read(p)
+}
+
+func (b *batchReadCloser) Close() error {
+	return b.crdReadCloser.Close() // echo backend's io.Closer implementation is a noop one.
+}
diff --git a/cmd/crank/xpkg/push.go b/cmd/crank/xpkg/push.go
index 05fa9e676..8142be77d 100644
--- a/cmd/crank/xpkg/push.go
+++ b/cmd/crank/xpkg/push.go
@@ -93,12 +93,7 @@ func (c *pushCmd) AfterApply() error {
 }
 
 // Run runs the push cmd.
-func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocognit // This feels easier to read as-is.
-	tag, err := name.NewTag(c.Package, name.StrictValidation)
-	if err != nil {
-		return errors.Wrapf(err, errFmtNewTag, c.Package)
-	}
-
+func (c *pushCmd) Run(logger logging.Logger) error {
 	// If package is not defined, attempt to find single package in current
 	// directory.
 	if len(c.PackageFiles) == 0 {
@@ -116,6 +111,19 @@ func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocognit // This f
 		logger.Debug("Found package in directory", "path", path)
 	}
 
+	// load images from all the provided package files
+	images := make([]packageImage, 0, len(c.PackageFiles))
+	for _, p := range c.PackageFiles {
+		cleanPath := filepath.Clean(p)
+
+		img, err := tarball.ImageFromPath(cleanPath, nil)
+		if err != nil {
+			return err
+		}
+
+		images = append(images, packageImage{Image: img, Path: cleanPath})
+	}
+
 	t := &http.Transport{
 		TLSClientConfig: &tls.Config{
 			InsecureSkipVerify: c.InsecureSkipTLSVerify, //nolint:gosec // we need to support insecure connections if requested
@@ -127,23 +135,46 @@ func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocognit // This f
 		remote.WithTransport(t),
 	}
 
-	// If there's only one package file, handle the simple path.
-	if len(c.PackageFiles) == 1 {
-		img, err := tarball.ImageFromPath(c.PackageFiles[0], nil)
-		if err != nil {
-			return errors.Wrapf(err, errFmtReadPackage, c.PackageFiles[0])
+	return pushImages(logger, images, c.Package, options...)
+}
+
+// packageImage describes a package image that will be pushed.
+type packageImage struct {
+	// The OCI Image of the package to be pushed.
+	Image v1.Image
+
+	// optional path for the image (e.g. file path on disk) to help provide more
+	// information about its source
+	Path string
+}
+
+// pushImages pushes package images to the given URL using the provided options.
+func pushImages(logger logging.Logger, images []packageImage, url string, options ...remote.Option) error {
+	if len(options) == 0 {
+		options = []remote.Option{
+			remote.WithAuthFromKeychain(authn.DefaultKeychain),
 		}
+	}
+
+	tag, err := name.NewTag(url, name.StrictValidation)
+	if err != nil {
+		return errors.Wrapf(err, errFmtNewTag, url)
+	}
+
+	// If there's only one package file, handle the simple path.
+	if len(images) == 1 {
+		pi := images[0]
 
-		img, err = xpkg.AnnotateLayers(img)
+		img, err := xpkg.AnnotateLayers(pi.Image)
 		if err != nil {
 			return errors.Wrapf(err, errAnnotateLayers)
 		}
 
 		if err := remote.Write(tag, img, options...); err != nil {
-			return errors.Wrapf(err, errFmtPushPackage, c.PackageFiles[0])
+			return errors.Wrapf(err, errFmtPushPackage, pi.Path)
 		}
 
-		logger.Debug("Pushed package", "path", c.PackageFiles[0], "ref", tag.String())
+		logger.Debug("Pushed package", "path", pi.Path, "ref", tag.String())
 
 		return nil
 	}
@@ -151,41 +182,36 @@ func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocognit // This f
 	// If there's more than one package file we'll write (push) them all by
 	// their digest, and create an index with the specified tag. This pattern is
 	// typically used to create a multi-platform image.
-	adds := make([]mutate.IndexAddendum, len(c.PackageFiles))
+	adds := make([]mutate.IndexAddendum, len(images))
 
 	g, ctx := errgroup.WithContext(context.Background())
-	for i, file := range c.PackageFiles {
+	for i, pi := range images {
 		g.Go(func() error {
-			img, err := tarball.ImageFromPath(filepath.Clean(file), nil)
-			if err != nil {
-				return errors.Wrapf(err, errFmtReadPackage, file)
-			}
-
-			img, err = xpkg.AnnotateLayers(img)
+			img, err := xpkg.AnnotateLayers(pi.Image)
 			if err != nil {
 				return errors.Wrapf(err, errAnnotateLayers)
 			}
 
 			d, err := img.Digest()
 			if err != nil {
-				return errors.Wrapf(err, errFmtGetDigest, file)
+				return errors.Wrapf(err, errFmtGetDigest, pi.Path)
 			}
 
 			n := fmt.Sprintf("%s@%s", tag.Repository.Name(), d.String())
 
 			ref, err := name.NewDigest(n, name.StrictValidation)
 			if err != nil {
-				return errors.Wrapf(err, errFmtNewDigest, n, file)
+				return errors.Wrapf(err, errFmtNewDigest, n, pi.Path)
 			}
 
 			mt, err := img.MediaType()
 			if err != nil {
-				return errors.Wrapf(err, errFmtGetMediaType, file)
+				return errors.Wrapf(err, errFmtGetMediaType, pi.Path)
 			}
 
 			conf, err := img.ConfigFile()
 			if err != nil {
-				return errors.Wrapf(err, errFmtGetConfigFile, file)
+				return errors.Wrapf(err, errFmtGetConfigFile, pi.Path)
 			}
 
 			adds[i] = mutate.IndexAddendum{
@@ -200,10 +226,10 @@ func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocognit // This f
 				},
 			}
 			if err := remote.Write(ref, img, append(options, remote.WithContext(ctx))...); err != nil {
-				return errors.Wrapf(err, errFmtPushPackage, file)
+				return errors.Wrapf(err, errFmtPushPackage, pi.Path)
 			}
 
-			logger.Debug("Pushed package", "path", file, "ref", ref.String())
+			logger.Debug("Pushed package", "path", pi.Path, "ref", ref.String())
 
 			return nil
 		})
diff --git a/cmd/crank/xpkg/xpkg.go b/cmd/crank/xpkg/xpkg.go
index ea4e680ba..c12a4e7b6 100644
--- a/cmd/crank/xpkg/xpkg.go
+++ b/cmd/crank/xpkg/xpkg.go
@@ -22,6 +22,7 @@ package xpkg
 // Cmd contains commands for interacting with xpkgs.
 type Cmd struct {
 	// Keep subcommands sorted alphabetically.
+	Batch   batchCmd   `cmd:"" help:"Batch build and push a family of provider packages."`
 	Build   buildCmd   `cmd:"" help:"Build a new package."`
 	Init    initCmd    `cmd:"" help:"Initialize a new package from a template."`
 	Install installCmd `cmd:"" help:"Install a package in a control plane."`
diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go
index 2a22a4a4a..ba2b7fc4e 100644
--- a/cmd/crossplane/core/core.go
+++ b/cmd/crossplane/core/core.go
@@ -126,10 +126,11 @@ type startCommand struct {
 	XfnCacheDir    string        `default:"/cache/xfn" env:"XFN_CACHE_DIR"     group:"Alpha Features:" help:"Directory used for caching function responses. Requires --enable-function-response-cache."`
 	XfnCacheMaxTTL time.Duration `default:"24h"        env:"XFN_CACHE_MAX_TTL" group:"Alpha Features:" help:"Maximum TTL for cached function responses. Set to 0 to disable. Requires --enable-function-response-cache."`
 
-	EnableDeploymentRuntimeConfigs bool `default:"true" group:"Beta Features:" help:"Enable support for Deployment Runtime Configs."`
-	EnableUsages                   bool `default:"true" group:"Beta Features:" help:"Enable support for deletion ordering and resource protection with Usages."`
-	EnableSSAClaims                bool `default:"true" group:"Beta Features:" help:"Enable support for using Kubernetes server-side apply to sync claims with composite resources (XRs)."`
-	EnableRealtimeCompositions     bool `default:"true" group:"Beta Features:" help:"Enable support for realtime compositions, i.e. watching composed resources and reconciling compositions immediately when any of the composed resources is updated."`
+	EnableDeploymentRuntimeConfigs          bool `default:"true" group:"Beta Features:" help:"Enable support for Deployment Runtime Configs."`
+	EnableUsages                            bool `default:"true" group:"Beta Features:" help:"Enable support for deletion ordering and resource protection with Usages."`
+	EnableSSAClaims                         bool `default:"true" group:"Beta Features:" help:"Enable support for using Kubernetes server-side apply to sync claims with composite resources (XRs)."`
+	EnableRealtimeCompositions              bool `default:"true" group:"Beta Features:" help:"Enable support for realtime compositions, i.e. watching composed resources and reconciling compositions immediately when any of the composed resources is updated."`
+	EnableCustomToManagedResourceConversion bool `default:"true" group:"Beta Features:" help:"Enable support CRD to MRD conversion when installing a package."`
 
 	// These are features that we've removed support for. Crossplane returns an
 	// error when you enable them. This ensures you'll see an explicit and
@@ -287,6 +288,11 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 		log.Info("Beta feature enabled", "flag", features.EnableBetaRealtimeCompositions)
 	}
 
+	if c.EnableCustomToManagedResourceConversion {
+		o.Features.Enable(features.EnableBetaCustomToManagedResourceConversion)
+		log.Info("Beta feature enabled", "flag", features.EnableBetaCustomToManagedResourceConversion)
+	}
+
 	if c.EnableDeploymentRuntimeConfigs {
 		o.Features.Enable(features.EnableBetaDeploymentRuntimeConfigs)
 		log.Info("Beta feature enabled", "flag", features.EnableBetaDeploymentRuntimeConfigs)
@@ -427,8 +433,9 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 
 	if o.Features.Enabled(features.EnableAlphaOperations) {
 		oo := opscontroller.Options{
-			Options:        o,
-			FunctionRunner: runner,
+			Options:          o,
+			FunctionRunner:   runner,
+			ControllerEngine: ce,
 		}
 		if err := ops.Setup(mgr, oo); err != nil {
 			return errors.Wrap(err, "cannot setup ops controllers")
diff --git a/cmd/crossplane/core/init.go b/cmd/crossplane/core/init.go
index c820363fb..73d53a2c3 100644
--- a/cmd/crossplane/core/init.go
+++ b/cmd/crossplane/core/init.go
@@ -29,18 +29,21 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 	"github.com/crossplane/crossplane-runtime/pkg/logging"
 
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
 	"github.com/crossplane/crossplane/internal/initializer"
 )
 
 // initCommand configuration for the initialization of core Crossplane controllers.
 type initCommand struct {
-	Providers                 []string `help:"Pre-install a Provider by giving its image URI. This argument can be repeated."      name:"provider"`
-	Configurations            []string `help:"Pre-install a Configuration by giving its image URI. This argument can be repeated." name:"configuration"`
-	Functions                 []string `help:"Pre-install a Function by giving its image URI. This argument can be repeated."      name:"function"`
-	Namespace                 string   `default:"crossplane-system"                                                                env:"POD_NAMESPACE"        help:"Namespace used to set as default scope in default secret store config." short:"n"`
-	ServiceAccount            string   `default:"crossplane"                                                                       env:"POD_SERVICE_ACCOUNT"  help:"Name of the Crossplane Service Account."`
-	CRDsPath                  string   `default:"/crds"                                                                            env:"CRDS_PATH"            help:"Path of Crossplane core Custom Resource Definitions."`
-	WebhookConfigurationsPath string   `default:"/webhookconfigurations"                                                           env:"WEBHOOK_CONFIGS_PATH" help:"Path of Crossplane core Webhook Configurations."`
+	Providers      []string                    `help:"Pre-install a Provider by giving its image URI. This argument can be repeated."                                            name:"provider"`
+	Configurations []string                    `help:"Pre-install a Configuration by giving its image URI. This argument can be repeated."                                       name:"configuration"`
+	Functions      []string                    `help:"Pre-install a Function by giving its image URI. This argument can be repeated."                                            name:"function"`
+	Activations    []v2alpha1.ActivationPolicy `help:"Pre-install a default managed resource activation policy by providing activations entries. This argument can be repeated." name:"activation"`
+
+	Namespace                 string `default:"crossplane-system"      env:"POD_NAMESPACE"        help:"Namespace used to set as default scope in default secret store config." short:"n"`
+	ServiceAccount            string `default:"crossplane"             env:"POD_SERVICE_ACCOUNT"  help:"Name of the Crossplane Service Account."`
+	CRDsPath                  string `default:"/crds"                  env:"CRDS_PATH"            help:"Path of Crossplane core Custom Resource Definitions."`
+	WebhookConfigurationsPath string `default:"/webhookconfigurations" env:"WEBHOOK_CONFIGS_PATH" help:"Path of Crossplane core Webhook Configurations."`
 
 	WebhookEnabled          bool   `default:"true"                   env:"WEBHOOK_ENABLED"                                                                         help:"Enable webhook configuration."`
 	WebhookServiceName      string `env:"WEBHOOK_SERVICE_NAME"       help:"The name of the Service object that the webhook service will be run."`
@@ -123,6 +126,7 @@ func (c *initCommand) Run(s *runtime.Scheme, log logging.Logger) error {
 	steps = append(steps, initializer.NewLockObject(),
 		initializer.NewPackageInstaller(c.Providers, c.Configurations, c.Functions),
 		initializer.StepFunc(initializer.DefaultDeploymentRuntimeConfig),
+		initializer.DefaultManagedResourceActivationPolicy(c.Activations...),
 	)
 
 	if err := initializer.New(cl, log, steps...).Init(context.TODO()); err != nil {
diff --git a/generate.go b/generate.go
index 79e246515..aeb350400 100644
--- a/generate.go
+++ b/generate.go
@@ -46,7 +46,7 @@ limitations under the License.
 
 // Generate deepcopy methodsets and CRD manifests
 //go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/pkg/v1beta1;./apis/pkg/v1 crd:crdVersions=v1,generateEmbeddedObjectMeta=true output:artifacts:config=./cluster/crds
-//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/apiextensions/v1alpha1;./apis/apiextensions/v1beta1;./apis/apiextensions/v1;./apis/apiextensions/v2alpha1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds
+//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/apiextensions/v1alpha1;./apis/apiextensions/v1beta1;./apis/apiextensions/v1;./apis/apiextensions/v2alpha1;./apis/apiextensions/v2 crd:crdVersions=v1 output:artifacts:config=./cluster/crds
 //go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/protection/v1beta1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds
 //go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/ops/v1alpha1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds
 
diff --git a/go.mod b/go.mod
index fc8daa9b4..8e52fca40 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@ require (
 	github.com/jmattheis/goverter v1.8.0
 	github.com/pkg/errors v0.9.1
 	github.com/posener/complete v1.2.3
+	github.com/robfig/cron/v3 v3.0.1
 	github.com/sigstore/cosign/v2 v2.2.4
 	github.com/sigstore/sigstore v1.9.4
 	github.com/sirupsen/logrus v1.9.3
@@ -179,7 +180,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/sso v1.21.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.25.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.29.1 // indirect
-	github.com/aws/smithy-go v1.20.2 // indirect
+	github.com/aws/smithy-go v1.20.2
 	github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
diff --git a/go.sum b/go.sum
index 0f179d758..f34af929d 100644
--- a/go.sum
+++ b/go.sum
@@ -604,6 +604,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
 github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
diff --git a/internal/controller/apiextensions/activationpolicy/reconciler.go b/internal/controller/apiextensions/activationpolicy/reconciler.go
new file mode 100644
index 000000000..280f70129
--- /dev/null
+++ b/internal/controller/apiextensions/activationpolicy/reconciler.go
@@ -0,0 +1,161 @@
+/*
+Copyright 2025 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 activationpolicy manages the lifecycle of MRAP controllers.
+package activationpolicy
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/resource"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+const (
+	timeout = 2 * time.Minute
+
+	errGetMRAP              = "cannot get ManagedResourceActivationPolicy"
+	errListMRD              = "cannot list ManagedResourceDefinition"
+	errUpdateStatus         = "cannot update status of ManagedResourceDefinition"
+	errFailedToActivateMRDs = "failed to activate %d of %d ManagedResourceDefinitions"
+)
+
+// Event reasons.
+const (
+	reasonPaused       event.Reason = "ReconciliationPaused"
+	reasonActivatedMRD event.Reason = "ActivateManagedResourceDefinition"
+
+	// Messages.
+	reconcilePausedMsg          = "Reconciliation is paused via the pause annotation"
+	reconcileActivateSuccessMsg = "Successfully activated ManagedResourceDefinition"
+)
+
+// A Reconciler reconciles CompositeResourceDefinitions.
+type Reconciler struct {
+	client.Client
+
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
+}
+
+// Reconcile a ManagedResourceActivationPolicy, matched ManagedResourceDefinitions are set to Active.
+func (r *Reconciler) Reconcile(ogctx context.Context, req reconcile.Request) (reconcile.Result, error) {
+	log := r.log.WithValues("request", req)
+	log.Debug("Reconciling")
+
+	ctx, cancel := context.WithTimeout(ogctx, timeout)
+	defer cancel()
+
+	mrap := &v2alpha1.ManagedResourceActivationPolicy{}
+	if err := r.Get(ctx, req.NamespacedName, mrap); err != nil {
+		// In case object is not found, most likely the object was deleted and
+		// then disappeared while the event was in the processing queue. We
+		// don't need to take any action in that case.
+		log.Debug(errGetMRAP, "error", err)
+		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetMRAP)
+	}
+
+	status := r.conditions.For(mrap)
+
+	log = log.WithValues(
+		"uid", mrap.GetUID(),
+		"version", mrap.GetResourceVersion(),
+		"name", mrap.GetName(),
+	)
+
+	if meta.WasDeleted(mrap) {
+		status.MarkConditions(v2alpha1.TerminatingActivationPolicy())
+		if err := r.Status().Update(ogctx, mrap); err != nil {
+			log.Debug(errUpdateStatus, "error", err)
+			if kerrors.IsConflict(err) {
+				return reconcile.Result{Requeue: true}, nil
+			}
+			err = errors.Wrap(err, errUpdateStatus)
+			return reconcile.Result{}, err
+		}
+
+		return reconcile.Result{}, nil
+	}
+	// Check for pause annotation
+	if meta.IsPaused(mrap) {
+		log.Info("reconciliation is paused")
+		r.record.Event(mrap, event.Normal(reasonPaused, reconcilePausedMsg))
+		return reconcile.Result{}, nil
+	}
+
+	// List all MRDs
+	mrds := &v2alpha1.ManagedResourceDefinitionList{}
+	if err := r.List(ctx, mrds); err != nil {
+		log.Debug(errListMRD, "error", err)
+
+		status.MarkConditions(v2alpha1.BlockedActivationPolicy().WithMessage(errListMRD))
+		if err := r.Status().Update(ogctx, mrap); err != nil {
+			log.Debug(errUpdateStatus, "error", err)
+			if kerrors.IsConflict(err) {
+				return reconcile.Result{Requeue: true}, nil
+			}
+			err = errors.Wrap(err, errUpdateStatus)
+			return reconcile.Result{}, err
+		}
+
+		return reconcile.Result{}, errors.Wrap(err, errListMRD)
+	}
+
+	// Start fresh.
+	mrap.Status.ClearActivated()
+
+	// For each, see if it is activated by the activation policy.
+	var errs []error
+	for _, mrd := range mrds.Items {
+		if mrap.Activates(mrd.GetName()) {
+			if mrd.Spec.State != v2alpha1.ManagedResourceDefinitionActive {
+				orig := mrd.DeepCopy()
+				mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+				// Patch to ignore any other updates. Just focused on the spec.state value.
+				if err := r.Patch(ctx, &mrd, client.MergeFrom(orig)); err != nil {
+					log.Debug("Error when patching the mrd to set state to active", "err", err)
+					errs = append(errs, err)
+					r.record.Event(mrap, event.Warning(reasonActivatedMRD, err))
+					continue
+				}
+				r.record.Event(mrap, event.Normal(reasonActivatedMRD, reconcileActivateSuccessMsg))
+			}
+			mrap.Status.AppendActivated(mrd.GetName())
+		}
+	}
+	if errs != nil {
+		status.MarkConditions(v2alpha1.Unhealthy().WithMessage(
+			fmt.Sprintf(errFailedToActivateMRDs, len(errs), len(mrap.Status.Activated))))
+	} else {
+		status.MarkConditions(v2alpha1.Healthy())
+	}
+
+	// TODO: we should really do a diff of the status to see if we should update or not.
+	return reconcile.Result{}, errors.Wrap(r.Status().Update(ogctx, mrap), errUpdateStatus)
+}
diff --git a/internal/controller/apiextensions/activationpolicy/setup.go b/internal/controller/apiextensions/activationpolicy/setup.go
new file mode 100644
index 000000000..0a616f095
--- /dev/null
+++ b/internal/controller/apiextensions/activationpolicy/setup.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2025 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 activationpolicy
+
+import (
+	"strings"
+
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/apis/apiextensions/v2alpha1"
+	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
+)
+
+// Setup adds a controller that reconciles CompositeResourceDefinitions by
+// defining a composite resource and starting a controller to reconcile it.
+func Setup(mgr ctrl.Manager, o apiextensionscontroller.Options) error {
+	name := "mrap/" + strings.ToLower(v2alpha1.ManagedResourceActivationPolicyKind)
+
+	r := NewReconciler(mgr,
+		WithLogger(o.Logger.WithValues("controller", name)),
+		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
+	)
+
+	return ctrl.NewControllerManagedBy(mgr).
+		Named(name).
+		For(&v2alpha1.ManagedResourceActivationPolicy{}).
+		Watches(&v2alpha1.ManagedResourceDefinition{},
+			EnqueueActivationPolicyForManagedResourceDefinition(mgr.GetClient(), r.log)).
+		WithOptions(o.ForControllerRuntime()).
+		Complete(errors.WithSilentRequeueOnConflict(r))
+}
+
+// ReconcilerOption is used to configure the Reconciler.
+type ReconcilerOption func(*Reconciler)
+
+// WithLogger specifies how the Reconciler should log messages.
+func WithLogger(log logging.Logger) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.log = log
+	}
+}
+
+// WithRecorder specifies how the Reconciler should record Kubernetes events.
+func WithRecorder(er event.Recorder) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.record = er
+	}
+}
+
+// NewReconciler returns a Reconciler of CompositeResourceDefinitions.
+func NewReconciler(mgr ctrl.Manager, opts ...ReconcilerOption) *Reconciler {
+	r := &Reconciler{
+		Client: mgr.GetClient(),
+
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
+	}
+
+	for _, f := range opts {
+		f(r)
+	}
+
+	return r
+}
diff --git a/internal/controller/apiextensions/activationpolicy/watch.go b/internal/controller/apiextensions/activationpolicy/watch.go
new file mode 100644
index 000000000..4cddc69d9
--- /dev/null
+++ b/internal/controller/apiextensions/activationpolicy/watch.go
@@ -0,0 +1,60 @@
+/*
+Copyright 2025 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 activationpolicy
+
+import (
+	"context"
+
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+// EnqueueActivationPolicyForManagedResourceDefinition enqueues a reconcile for policies that apply to a managed resource definition.
+func EnqueueActivationPolicyForManagedResourceDefinition(kube client.Client, log logging.Logger) handler.EventHandler {
+	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
+		mrd, ok := o.(*v2alpha1.ManagedResourceDefinition)
+		if !ok {
+			return nil
+		}
+
+		policies := &v2alpha1.ManagedResourceActivationPolicyList{}
+		if err := kube.List(ctx, policies); err != nil {
+			// Nothing we can do, except logging, if we can't list managed resource activation policies
+			log.Debug("Cannot list managed resource activation policies while attempting to enqueue requests", "error", err)
+			return nil
+		}
+
+		var matches []reconcile.Request
+
+		for _, policy := range policies.Items {
+			if policy.Activates(mrd.GetName()) {
+				log.Debug("Enqueuing for managed resource definition",
+					"mrd-name", mrd.GetName(),
+					"policy-name", policy.GetName())
+				matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: policy.GetName()}})
+			}
+		}
+
+		return matches
+	})
+}
diff --git a/internal/controller/apiextensions/apiextensions.go b/internal/controller/apiextensions/apiextensions.go
index e5bdf68ce..b77c5c8dc 100644
--- a/internal/controller/apiextensions/apiextensions.go
+++ b/internal/controller/apiextensions/apiextensions.go
@@ -20,9 +20,11 @@ package apiextensions
 import (
 	ctrl "sigs.k8s.io/controller-runtime"
 
+	"github.com/crossplane/crossplane/internal/controller/apiextensions/activationpolicy"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/composition"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/definition"
+	"github.com/crossplane/crossplane/internal/controller/apiextensions/managed"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/offered"
 	"github.com/crossplane/crossplane/internal/controller/apiextensions/revision"
 )
@@ -41,5 +43,13 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
 		return err
 	}
 
+	if err := managed.Setup(mgr, o); err != nil {
+		return err
+	}
+
+	if err := activationpolicy.Setup(mgr, o); err != nil {
+		return err
+	}
+
 	return offered.Setup(mgr, o)
 }
diff --git a/internal/controller/apiextensions/composite/composition_functions.go b/internal/controller/apiextensions/composite/composition_functions.go
index d2abd8825..41c3f5e93 100644
--- a/internal/controller/apiextensions/composite/composition_functions.go
+++ b/internal/controller/apiextensions/composite/composition_functions.go
@@ -58,7 +58,7 @@ const (
 	errGarbageCollectCDs        = "cannot garbage collect composed resources that are no longer desired"
 	errApplyXRRefs              = "cannot update composed resource references"
 	errApplyXRStatus            = "cannot apply composite resource status"
-	errAnonymousCD              = "encountered composed resource without required \"" + AnnotationKeyCompositionResourceName + "\" annotation"
+	errAnonymousCD              = "encountered composed resource without required \"" + xcrd.AnnotationKeyCompositionResourceName + "\" annotation"
 	errUnmarshalDesiredXRStatus = "cannot unmarshal desired composite resource status from RunFunctionResponse"
 	errXRAsStruct               = "cannot encode composite resource to protocol buffer Struct well-known type"
 	errStructFromUnstructured   = "cannot create Struct"
@@ -81,6 +81,7 @@ const (
 	errFmtInvalidName                 = "cannot apply composed resource %q because it has an invalid name %q. Must be a valid RFC 1123 subdomain name."
 	errFmtGetResourceMapping          = "cannot check if composed resource %q is namespaced (a %s named %s)"
 	errFmtNamespacedXRClusterResource = "cannot apply cluster scoped composed resource %q (a %s named %s) for a namespaced composite resource."
+	errFmtFetchBootstrapRequirements  = "cannot fetch bootstrap required resources for requirement %q"
 )
 
 // Server-side-apply field owners. We need two of these because it's possible
@@ -106,6 +107,7 @@ type FunctionComposer struct {
 	client    client.Client
 	composite xr
 	pipeline  FunctionRunner
+	resources xfn.RequiredResourcesFetcher
 }
 
 type xr struct {
@@ -215,6 +217,14 @@ func WithManagedFieldsUpgrader(u ManagedFieldsUpgrader) FunctionComposerOption {
 	}
 }
 
+// WithRequiredResourcesFetcher configures how the FunctionComposer should
+// fetch required resources for composition functions.
+func WithRequiredResourcesFetcher(f xfn.RequiredResourcesFetcher) FunctionComposerOption {
+	return func(p *FunctionComposer) {
+		p.resources = f
+	}
+}
+
 // 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(cached, uncached client.Client, r FunctionRunner, o ...FunctionComposerOption) *FunctionComposer {
@@ -231,7 +241,8 @@ func NewFunctionComposer(cached, uncached client.Client, r FunctionRunner, o ...
 			ManagedFieldsUpgrader:            NewPatchingManagedFieldsUpgrader(cached),
 		},
 
-		pipeline: r,
+		pipeline:  r,
+		resources: xfn.NewExistingRequiredResourcesFetcher(cached),
 	}
 
 	for _, fn := range o {
@@ -320,6 +331,18 @@ func (c *FunctionComposer) Compose(ctx context.Context, xr *composite.Unstructur
 			}
 		}
 
+		// Pre-populate bootstrap requirements
+		if fn.Requirements != nil {
+			req.RequiredResources = map[string]*fnv1.Resources{}
+			for _, sel := range fn.Requirements.RequiredResources {
+				resources, err := c.resources.Fetch(ctx, ToProtobufResourceSelector(sel))
+				if err != nil {
+					return CompositionResult{}, errors.Wrapf(err, errFmtFetchBootstrapRequirements, sel.RequirementName)
+				}
+				req.RequiredResources[sel.RequirementName] = resources
+			}
+		}
+
 		req.Meta = &fnv1.RequestMeta{Tag: Tag(req)}
 
 		rsp, err := c.pipeline.RunFunction(ctx, fn.FunctionRef.Name, req)
@@ -611,6 +634,33 @@ func (c *FunctionComposer) Compose(ctx context.Context, xr *composite.Unstructur
 	return result, nil
 }
 
+// ToProtobufResourceSelector converts API RequiredResourceSelector to protobuf ResourceSelector.
+func ToProtobufResourceSelector(r v1.RequiredResourceSelector) *fnv1.ResourceSelector {
+	selector := &fnv1.ResourceSelector{
+		ApiVersion: r.APIVersion,
+		Kind:       r.Kind,
+		Namespace:  r.Namespace,
+	}
+
+	// You can only set one of name or matchLabels.
+	if r.Name != nil {
+		selector.Match = &fnv1.ResourceSelector_MatchName{
+			MatchName: *r.Name,
+		}
+		return selector
+	}
+
+	if len(r.MatchLabels) > 0 {
+		selector.Match = &fnv1.ResourceSelector_MatchLabels{
+			MatchLabels: &fnv1.MatchLabels{
+				Labels: r.MatchLabels,
+			},
+		}
+	}
+
+	return selector
+}
+
 // Tag uniquely identifies a request. Two identical requests created by the
 // same Crossplane binary will produce identical tags. Different builds of
 // Crossplane may produce different tags for the same inputs. See the docs for
@@ -720,7 +770,7 @@ func (g *ExistingComposedResourceObserver) ObserveComposedResources(ctx context.
 			continue
 		}
 
-		name := GetCompositionResourceName(r)
+		name := ResourceName(xcrd.GetCompositionResourceName(r))
 		if name == "" {
 			return nil, errors.New(errAnonymousCD)
 		}
diff --git a/internal/controller/apiextensions/composite/composition_render.go b/internal/controller/apiextensions/composite/composition_render.go
index b2686bc13..781421b3a 100644
--- a/internal/controller/apiextensions/composite/composition_render.go
+++ b/internal/controller/apiextensions/composite/composition_render.go
@@ -97,7 +97,7 @@ func RenderComposedResourceMetadata(cd, xr resource.Object, n ResourceName) erro
 	}
 
 	if n != "" {
-		SetCompositionResourceName(cd, n)
+		xcrd.SetCompositionResourceName(cd, string(n))
 	}
 
 	// TODO(negz): What happens if there is no claim? Will this set empty
diff --git a/internal/controller/apiextensions/managed/reconciler.go b/internal/controller/apiextensions/managed/reconciler.go
new file mode 100644
index 000000000..c507acb52
--- /dev/null
+++ b/internal/controller/apiextensions/managed/reconciler.go
@@ -0,0 +1,215 @@
+/*
+Copyright 2025 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 managed manages the lifecycle of MRD controllers.
+package managed
+
+import (
+	"context"
+	"time"
+
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/resource"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+	"github.com/crossplane/crossplane/internal/controller/apiextensions/managed/resources"
+	"github.com/crossplane/crossplane/internal/xcrd"
+)
+
+const (
+	timeout = 2 * time.Minute
+)
+
+// Event reasons.
+const (
+	reasonReconcile event.Reason = "Reconcile"
+	reasonPaused    event.Reason = "ReconciliationPaused"
+	reasonCreateCRD event.Reason = "CreateCustomResourceDefinition"
+	reasonUpdateCRD event.Reason = "UpdateCustomResourceDefinition"
+)
+
+// A Reconciler reconciles CompositeResourceDefinitions.
+type Reconciler struct {
+	client.Client
+
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
+}
+
+// Reconcile a CompositeResourceDefinition by defining a new kind of composite
+// resource and starting a controller to reconcile it.
+func (r *Reconciler) Reconcile(ogctx context.Context, req reconcile.Request) (reconcile.Result, error) {
+	log := r.log.WithValues("request", req)
+	log.Debug("Reconciling")
+
+	ctx, cancel := context.WithTimeout(ogctx, timeout)
+	defer cancel()
+
+	mrd := &v2alpha1.ManagedResourceDefinition{}
+	if err := r.Get(ctx, req.NamespacedName, mrd); err != nil {
+		// In case object is not found, most likely the object was deleted and
+		// then disappeared while the event was in the processing queue. We
+		// don't need to take any action in that case.
+		log.Debug("cannot get ManagedResourceDefinition", "error", err)
+		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), "cannot get ManagedResourceDefinition")
+	}
+
+	status := r.conditions.For(mrd)
+
+	log = log.WithValues(
+		"uid", mrd.GetUID(),
+		"version", mrd.GetResourceVersion(),
+		"name", mrd.GetName(),
+	)
+
+	if meta.WasDeleted(mrd) {
+		status.MarkConditions(v2alpha1.TerminatingManaged())
+		if err := r.Status().Update(ogctx, mrd); err != nil {
+			log.Debug("cannot update status of ManagedResourceDefinition", "error", err)
+			if kerrors.IsConflict(err) {
+				return reconcile.Result{Requeue: true}, nil
+			}
+			err = errors.Wrap(err, "cannot update status of ManagedResourceDefinition")
+			return reconcile.Result{}, err
+		}
+
+		return reconcile.Result{}, nil
+	}
+	// Check for pause annotation
+	if meta.IsPaused(mrd) {
+		log.Info("reconciliation is paused")
+		r.record.Event(mrd, event.Normal(reasonPaused, "Reconciliation is paused via the pause annotation"))
+		return reconcile.Result{}, nil
+	}
+
+	if !mrd.Spec.State.IsActive() {
+		status.MarkConditions(v2alpha1.InactiveManaged())
+		return reconcile.Result{}, errors.Wrap(r.Status().Update(ogctx, mrd), "cannot update status of ManagedResourceDefinition")
+	}
+
+	// Check that the CRD exists and is up to date.
+	crd, err := r.reconcileCustomResourceDefinition(ctx, log, mrd)
+	if err != nil {
+		log.Debug("failed to reconcile CustomResourceDefinition", "error", err)
+		r.record.Event(mrd, event.Warning(reasonReconcile, err))
+		status.MarkConditions(v2alpha1.BlockedManaged().WithMessage("unable to reconcile CustomResourceDefinition, see events"))
+		if err := r.Status().Update(ogctx, mrd); err != nil {
+			log.Info("cannot update status of ManagedResourceDefinition", "error", err)
+		}
+		return reconcile.Result{}, errors.Wrap(err, "cannot reconcile CustomResourceDefinition")
+	}
+
+	if xcrd.IsEstablished(crd.Status) {
+		status.MarkConditions(v2alpha1.EstablishedManaged())
+	} else {
+		log.Debug("waiting for managed resource CustomResourceDefinition to be established")
+		status.MarkConditions(v2alpha1.PendingManaged())
+	}
+
+	return reconcile.Result{}, errors.Wrap(r.Status().Update(ogctx, mrd), "cannot update status of ManagedResourceDefinition")
+}
+
+const (
+	actionCreate = iota
+	actionUpdate
+)
+
+func (r *Reconciler) reconcileCustomResourceDefinition(ctx context.Context, log logging.Logger, mrd *v2alpha1.ManagedResourceDefinition) (*extv1.CustomResourceDefinition, error) {
+	want := resources.EmptyCustomResourceDefinition(mrd)
+	nn := types.NamespacedName{
+		Namespace: want.Namespace,
+		Name:      want.Name,
+	}
+	action := actionUpdate
+	if err := r.Get(ctx, nn, want); err != nil && !kerrors.IsNotFound(err) {
+		return nil, errors.Wrap(err, "cannot get CustomResourceDefinition")
+	} else if err != nil && kerrors.IsNotFound(err) {
+		log.Debug("CustomResourceDefinition not found, will create", "crd", nn)
+		action = actionCreate
+	}
+	existing := want.DeepCopy()
+
+	if meta.WasDeleted(want) {
+		log.Debug("CustomResourceDefinition is being deleted", "crd", nn)
+		return nil, errors.New("crd was deleted")
+	}
+
+	// Own the CRD.
+	meta.AddOwnerReference(want, meta.AsOwner(meta.TypedReferenceTo(mrd, v2alpha1.ManagedResourceDefinitionGroupVersionKind)))
+	// But also propagate our controller as the controller of the CRD.
+	if owner := metav1.GetControllerOf(mrd); owner != nil {
+		meta.AddOwnerReference(want, *owner)
+	}
+
+	// Stage changes.
+	if err := resources.MergeCustomResourceDefinitionInto(mrd, want); err != nil {
+		return nil, errors.Wrap(err, "cannot merge CustomResourceDefinition")
+	}
+
+	// Apply changes.
+	switch action {
+	case actionCreate:
+		if err := r.Create(ctx, want); err != nil {
+			return nil, r.handleErrorWithEvent(log, mrd, err, "cannot create CustomResourceDefinition", reasonCreateCRD)
+		}
+		r.record.Event(mrd, event.Normal(reasonCreateCRD, "Successfully created CustomResourceDefinition"))
+	case actionUpdate:
+		if !equality.Semantic.DeepEqual(existing.Spec, want.Spec) {
+			if err := r.Update(ctx, want); err != nil {
+				return nil, r.handleErrorWithEvent(log, mrd, err, "cannot update CustomResourceDefinition", reasonUpdateCRD)
+			}
+			r.record.Event(mrd, event.Normal(reasonUpdateCRD, "Successfully updated CustomResourceDefinition"))
+		}
+	default:
+		// Noop.
+	}
+
+	return want, nil
+}
+
+// handleErrorWithEvent provides centralized error handling with event recording.
+func (r *Reconciler) handleErrorWithEvent(log logging.Logger, obj client.Object, err error, errMsg string, reason event.Reason) error {
+	// Handle conflicts with immediate requeue - no event needed as this is transient
+	if kerrors.IsConflict(err) {
+		return err // Controller runtime will requeue automatically
+	}
+
+	// Record warning event for all other errors
+	r.record.Event(obj, event.Warning(reason, err))
+	log.Debug(errMsg, "reason", reason, "error", err)
+
+	// Handle validation errors to prevent endless reconciliation
+	if kerrors.IsInvalid(err) {
+		// API Server's invalid errors may be unstable due to pointers in
+		// the string representation, causing endless reconciliation
+		err = errors.New("invalid resource configuration")
+	}
+
+	return errors.Wrap(err, errMsg)
+}
diff --git a/internal/controller/apiextensions/managed/resources/custom_resource_definition.go b/internal/controller/apiextensions/managed/resources/custom_resource_definition.go
new file mode 100644
index 000000000..319f66603
--- /dev/null
+++ b/internal/controller/apiextensions/managed/resources/custom_resource_definition.go
@@ -0,0 +1,99 @@
+/*
+Copyright 2025 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 resources contains static methods to help render resources for the MRD reconciler.
+package resources
+
+import (
+	"encoding/json"
+	"slices"
+	"strings"
+
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+const (
+	errParseValidation             = "cannot parse custom resource validation OpenAPIV3Schema"
+	errCustomResourceValidationNil = "custom resource validation cannot be nil"
+)
+
+// EmptyCustomResourceDefinition returns an empty CRD named in line with the MRD.
+func EmptyCustomResourceDefinition(mrd *v2alpha1.ManagedResourceDefinition) *extv1.CustomResourceDefinition {
+	return &extv1.CustomResourceDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: mrd.GetName(),
+		},
+	}
+}
+
+// MergeCustomResourceDefinitionInto updates the given crd to match the given mrd.
+func MergeCustomResourceDefinitionInto(mrd *v2alpha1.ManagedResourceDefinition, crd *extv1.CustomResourceDefinition) error {
+	want := mrd.Spec.CustomResourceDefinitionSpec
+
+	crd.Spec.Group = want.Group
+	crd.Spec.Names = want.Names
+	crd.Spec.Scope = want.Scope
+	crd.Spec.Conversion = want.Conversion
+	crd.Spec.PreserveUnknownFields = want.PreserveUnknownFields
+
+	crd.Spec.Versions = make([]extv1.CustomResourceDefinitionVersion, len(want.Versions))
+	for i, version := range want.Versions {
+		schema, err := toCustomResourceValidation(version.Schema)
+		if err != nil {
+			return err
+		}
+		crd.Spec.Versions[i] = extv1.CustomResourceDefinitionVersion{
+			Name:                     version.Name,
+			Served:                   version.Served,
+			Storage:                  version.Storage,
+			Deprecated:               version.Deprecated,
+			DeprecationWarning:       version.DeprecationWarning,
+			Schema:                   schema,
+			Subresources:             version.Subresources,
+			AdditionalPrinterColumns: version.AdditionalPrinterColumns,
+			SelectableFields:         version.SelectableFields,
+		}
+	}
+	// TODO: We are not merging above, we are replacing. If there is something that mutates the CRD, we will fight.
+
+	slices.SortFunc(crd.Spec.Versions, func(a, b extv1.CustomResourceDefinitionVersion) int {
+		return strings.Compare(a.Name, b.Name)
+	})
+
+	return nil
+}
+
+func toCustomResourceValidation(given *v2alpha1.CustomResourceValidation) (*extv1.CustomResourceValidation, error) {
+	if given == nil {
+		return nil, errors.New(errCustomResourceValidationNil)
+	}
+
+	schema := &extv1.JSONSchemaProps{}
+	if err := json.Unmarshal(given.OpenAPIV3Schema.Raw, schema); err != nil {
+		return nil, errors.Wrap(err, errParseValidation)
+	}
+
+	// TODO: We could choose to validate some of the CRD schema here or later.
+
+	return &extv1.CustomResourceValidation{
+		OpenAPIV3Schema: schema,
+	}, nil
+}
diff --git a/internal/controller/apiextensions/managed/setup.go b/internal/controller/apiextensions/managed/setup.go
new file mode 100644
index 000000000..4a491ac70
--- /dev/null
+++ b/internal/controller/apiextensions/managed/setup.go
@@ -0,0 +1,85 @@
+/*
+Copyright 2025 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 managed manages the lifecycle of MRD controllers.
+package managed
+
+import (
+	"strings"
+
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/apis/apiextensions/v2alpha1"
+	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
+)
+
+// Setup adds a controller that reconciles CompositeResourceDefinitions by
+// defining a composite resource and starting a controller to reconcile it.
+func Setup(mgr ctrl.Manager, o apiextensionscontroller.Options) error {
+	name := "mrd/" + strings.ToLower(v2alpha1.ManagedResourceDefinitionKind)
+
+	r := NewReconciler(mgr,
+		WithLogger(o.Logger.WithValues("controller", name)),
+		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
+	)
+
+	return ctrl.NewControllerManagedBy(mgr).
+		Named(name).
+		For(&v2alpha1.ManagedResourceDefinition{}).
+		Owns(&extv1.CustomResourceDefinition{}).
+		WithOptions(o.ForControllerRuntime()).
+		Complete(errors.WithSilentRequeueOnConflict(r))
+}
+
+// ReconcilerOption is used to configure the Reconciler.
+type ReconcilerOption func(*Reconciler)
+
+// WithLogger specifies how the Reconciler should log messages.
+func WithLogger(log logging.Logger) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.log = log
+	}
+}
+
+// WithRecorder specifies how the Reconciler should record Kubernetes events.
+func WithRecorder(er event.Recorder) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.record = er
+	}
+}
+
+// NewReconciler returns a Reconciler of CompositeResourceDefinitions.
+func NewReconciler(mgr ctrl.Manager, opts ...ReconcilerOption) *Reconciler {
+	r := &Reconciler{
+		Client: mgr.GetClient(),
+
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
+	}
+
+	for _, f := range opts {
+		f(r)
+	}
+
+	return r
+}
diff --git a/internal/controller/ops/controller/options.go b/internal/controller/ops/controller/options.go
index bea8d4ab9..8e6107808 100644
--- a/internal/controller/ops/controller/options.go
+++ b/internal/controller/ops/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"
 )
 
@@ -29,4 +30,7 @@ type Options struct {
 
 	// FunctionRunner used to run Composition Functions.
 	FunctionRunner xfn.FunctionRunner
+
+	// ControllerEngine used to dynamically manage watches.
+	ControllerEngine *engine.ControllerEngine
 }
diff --git a/internal/controller/ops/cronoperation/constructor.go b/internal/controller/ops/cronoperation/constructor.go
new file mode 100644
index 000000000..00037c7ee
--- /dev/null
+++ b/internal/controller/ops/cronoperation/constructor.go
@@ -0,0 +1,103 @@
+/*
+Copyright 2025 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 cronoperation implements cron-based day two operations.
+package cronoperation
+
+import (
+	"strings"
+	"time"
+
+	"github.com/robfig/cron/v3"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/ratelimiter"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	opscontroller "github.com/crossplane/crossplane/internal/controller/ops/controller"
+)
+
+// Setup adds a controller that reconciles CronOperations by
+// creating Operations on a cron schedule.
+func Setup(mgr ctrl.Manager, o opscontroller.Options) error {
+	name := "ops/" + strings.ToLower(v1alpha1.CronOperationGroupKind)
+
+	r := NewReconciler(mgr,
+		WithLogger(o.Logger.WithValues("controller", name)),
+		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))
+
+	return ctrl.NewControllerManagedBy(mgr).
+		Named(name).
+		For(&v1alpha1.CronOperation{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
+		Owns(&v1alpha1.Operation{}).
+		WithOptions(o.ForControllerRuntime()).
+		Complete(ratelimiter.NewReconciler(name, errors.WithSilentRequeueOnConflict(r), o.GlobalRateLimiter))
+}
+
+// ReconcilerOption is used to configure the Reconciler.
+type ReconcilerOption func(*Reconciler)
+
+// WithLogger specifies how the Reconciler should log messages.
+func WithLogger(log logging.Logger) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.log = log
+	}
+}
+
+// WithRecorder specifies how the Reconciler should record Kubernetes events.
+func WithRecorder(er event.Recorder) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.record = er
+	}
+}
+
+// WithScheduler specifies how the Reconciler should schedule operations.
+func WithScheduler(s Scheduler) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.schedule = s
+	}
+}
+
+// NewReconciler returns a Reconciler of CronOperations.
+func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
+	r := &Reconciler{
+		client:     mgr.GetClient(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
+		schedule: SchedulerFn(func(schedule string, last time.Time) (time.Time, error) {
+			cs, err := cron.ParseStandard(schedule)
+			if err != nil {
+				return time.Time{}, errors.Wrap(err, "cannot parse cron schedule")
+			}
+
+			return cs.Next(last), nil
+		}),
+	}
+
+	for _, f := range opts {
+		f(r)
+	}
+
+	return r
+}
diff --git a/internal/controller/ops/cronoperation/reconciler.go b/internal/controller/ops/cronoperation/reconciler.go
new file mode 100644
index 000000000..119871ac2
--- /dev/null
+++ b/internal/controller/ops/cronoperation/reconciler.go
@@ -0,0 +1,249 @@
+/*
+Copyright 2025 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 cronoperation
+
+import (
+	"context"
+	"fmt"
+	"maps"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/resource"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	"github.com/crossplane/crossplane/internal/ops/lifecycle"
+)
+
+// Event reasons.
+const (
+	reasonInvalidSchedule          = "InvalidSchedule"
+	reasonListOperations           = "ListOperations"
+	reasonGarbageCollectOperations = "GarbageCollectOperations"
+	reasonReplaceRunningOperation  = "ReplaceRunningOperation"
+	reasonCreateOperation          = "CreateOperation"
+)
+
+// A Scheduler determines when the next Operation should run.
+type Scheduler interface {
+	Next(schedule string, last time.Time) (time.Time, error)
+}
+
+// A SchedulerFn is a function that satisfies Scheduler.
+type SchedulerFn func(schedule string, last time.Time) (time.Time, error)
+
+// Next returns the next time an Operation should run.
+func (fn SchedulerFn) Next(schedule string, last time.Time) (time.Time, error) {
+	return fn(schedule, last)
+}
+
+// A Reconciler reconciles CronOperations.
+type Reconciler struct {
+	client     client.Client
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
+	schedule   Scheduler
+}
+
+// Reconcile a CronOperation.
+func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
+	log := r.log.WithValues("request", req)
+
+	co := &v1alpha1.CronOperation{}
+	if err := r.client.Get(ctx, req.NamespacedName, co); err != nil {
+		log.Debug("cannot get CronOperation", "error", err)
+		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), "cannot get CronOperation")
+	}
+
+	status := r.conditions.For(co)
+
+	log = log.WithValues(
+		"uid", co.GetUID(),
+		"version", co.GetResourceVersion(),
+		"name", co.GetName(),
+	)
+
+	// Don't reconcile if the CronOperation is being deleted.
+	if meta.WasDeleted(co) {
+		return reconcile.Result{Requeue: false}, nil
+	}
+
+	// Don't reconcile if the CronOperation is paused.
+	if meta.IsPaused(co) {
+		log.Debug("CronOperation is paused")
+		status.MarkConditions(v1alpha1.SchedulePaused(), xpv1.ReconcilePaused())
+		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, co), "cannot update CronOperation status")
+	}
+
+	ol := &v1alpha1.OperationList{}
+	if err := r.client.List(ctx, ol, client.MatchingLabels{v1alpha1.LabelCronOperationName: co.GetName()}); err != nil {
+		log.Debug("Cannot list Operations", "error", err)
+		err = errors.Wrap(err, "cannot list Operations")
+		r.record.Event(co, event.Warning(reasonListOperations, err))
+		status.MarkConditions(xpv1.ReconcileError(err))
+		_ = r.client.Status().Update(ctx, co)
+		return reconcile.Result{}, err
+	}
+
+	// Derive our last scheduled time from the last time we created an
+	// Operation.
+	if t := lifecycle.LatestCreateTime(ol.Items...); !t.IsZero() {
+		co.Status.LastScheduleTime = &metav1.Time{Time: t}
+	}
+
+	// If we recorded a last schedule time use it. If not, use the
+	// CronOperation's creation timestamp.
+	last := ptr.Deref(co.Status.LastScheduleTime, co.GetCreationTimestamp()).Time
+
+	// Record the last time an Operation succeeded, if any.
+	if t := lifecycle.LatestSucceededTransitionTime(lifecycle.WithReason(v1alpha1.ReasonPipelineSuccess, ol.Items...)...); !t.IsZero() {
+		co.Status.LastSuccessfulTime = &metav1.Time{Time: t}
+	}
+
+	// Record all running Operations.
+	running := make(map[string]bool)
+	for _, op := range lifecycle.WithReason(v1alpha1.ReasonPipelineRunning, ol.Items...) {
+		running[op.GetName()] = true
+	}
+	co.Status.RunningOperationRefs = lifecycle.RunningOperationRefs(slices.Sorted(maps.Keys(running)))
+
+	// Garbage collect Operations older than the history limits.
+	for _, op := range lifecycle.MarkGarbage(ptr.Deref(co.Spec.SuccessfulHistoryLimit, 3), ptr.Deref(co.Spec.FailedHistoryLimit, 1), ol.Items...) {
+		if err := r.client.Delete(ctx, &op); resource.IgnoreNotFound(err) != nil {
+			log.Debug("Cannot garbage collect Operation", "error", err, "operation", op.GetName())
+			err = errors.Wrapf(err, "cannot garbage collect Operation %q", op.GetName())
+			r.record.Event(co, event.Warning(reasonGarbageCollectOperations, err))
+			status.MarkConditions(xpv1.ReconcileError(err))
+			_ = r.client.Status().Update(ctx, co)
+			return reconcile.Result{}, err
+		}
+	}
+
+	next, err := r.schedule.Next(co.Spec.Schedule, last)
+	if err != nil {
+		r.log.Info("Invalid cron schedule", "error", err, "schedule", co.Spec.Schedule)
+		err = errors.Wrapf(err, "cannot parse cron schedule %q", co.Spec.Schedule)
+		r.record.Event(co, event.Warning(reasonInvalidSchedule, err))
+		status.MarkConditions(v1alpha1.ScheduleInvalid(err.Error()), xpv1.ReconcileError(err))
+
+		// We don't return the underlying error here because it's
+		// terminal. There's no point requeuing until someone fixes it.
+		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, co), "cannot update CronOperation status")
+	}
+
+	// Mark the schedule as active once we know it's valid
+	status.MarkConditions(v1alpha1.ScheduleActive())
+
+	// If the next scheduled operation is in the future, requeue for then.
+	now := time.Now()
+	if next.After(now) {
+		r.log.Debug("Next scheduled Operation is in the future - doing nothing", "scheduled-time", next)
+		return reconcile.Result{RequeueAfter: next.Sub(now)}, errors.Wrap(r.client.Status().Update(ctx, co), "cannot update CronOperation status")
+	}
+
+	// Figure out the next scheduled operation that's in the future. We know
+	// we won't hit an error parsing the schedule because it worked above.
+	future, _ := r.schedule.Next(co.Spec.Schedule, now)
+
+	// If the next scheduled operation is in the past, but we missed its
+	// deadline, requeue in time for the first one scheduled in the future.
+	if deadline := co.Spec.StartingDeadlineSeconds; deadline != nil {
+		grace := time.Duration(*deadline) * time.Second
+		if next.Add(grace).Before(now) {
+			r.log.Debug("Missed deadline for scheduled Operation - doing nothing", "scheduled-time", next, "deadline", next.Add(grace))
+			return reconcile.Result{RequeueAfter: future.Sub(now)}, errors.Wrap(r.client.Status().Update(ctx, co), "cannot update CronOperation status")
+		}
+	}
+
+	// At this point we know we're due to create an operation.
+
+	if len(running) > 0 {
+		switch p := ptr.Deref(co.Spec.ConcurrencyPolicy, v1alpha1.ConcurrencyPolicyAllow); p {
+		case v1alpha1.ConcurrencyPolicyAllow:
+			r.log.Debug("Concurrency policy allows creating scheduled Operation while other Operations are running", "policy", p, "running", len(running))
+		case v1alpha1.ConcurrencyPolicyForbid:
+			r.log.Debug("Concurrency policy forbids creating scheduled Operation while other Operations are running", "policy", p, "running", len(running))
+			return reconcile.Result{RequeueAfter: future.Sub(now)}, errors.Wrap(r.client.Status().Update(ctx, co), "cannot update CronOperation status")
+		case v1alpha1.ConcurrencyPolicyReplace:
+			r.log.Debug("Concurrency policy requires deleting other running Operations", "policy", p, "running", len(running))
+			for _, op := range ol.Items {
+				if !running[op.GetName()] {
+					continue
+				}
+				if err := r.client.Delete(ctx, &op); resource.IgnoreNotFound(err) != nil {
+					log.Debug("Cannot delete running Operation", "error", err, "operation", op.GetName())
+					err = errors.Wrapf(err, "cannot delete running Operation %q", op.GetName())
+					r.record.Event(co, event.Warning(reasonReplaceRunningOperation, err))
+					status.MarkConditions(xpv1.ReconcileError(err))
+					_ = r.client.Status().Update(ctx, co)
+					return reconcile.Result{}, err
+				}
+
+				r.log.Debug("Deleted running operation due to concurrency policy", "policy", p, "operation", op.GetName())
+			}
+		}
+	}
+
+	op := NewOperation(co, next)
+	if err := r.client.Create(ctx, op); err != nil {
+		log.Debug("Cannot create scheduled Operation", "error", err, "operation", op.GetName())
+		err = errors.Wrapf(err, "cannot create scheduled Operation %q", op.GetName())
+		r.record.Event(co, event.Warning(reasonCreateOperation, err))
+		status.MarkConditions(xpv1.ReconcileError(err))
+		_ = r.client.Status().Update(ctx, co)
+		return reconcile.Result{}, err
+	}
+
+	// We rely on our watch to add the new Operation to status at the top of
+	// the Reconcile.
+
+	status.MarkConditions(xpv1.ReconcileSuccess())
+	return reconcile.Result{RequeueAfter: future.Sub(now)}, errors.Wrap(r.client.Status().Update(ctx, co), "cannot update CronOperation status")
+}
+
+// NewOperation creates a new operation given the CronOperation's template.
+func NewOperation(co *v1alpha1.CronOperation, scheduled time.Time) *v1alpha1.Operation {
+	op := &v1alpha1.Operation{
+		ObjectMeta: co.Spec.OperationTemplate.ObjectMeta,
+		Spec:       co.Spec.OperationTemplate.Spec,
+	}
+
+	op.SetName(fmt.Sprintf("%s-%d", co.GetName(), scheduled.Unix()))
+	meta.AddLabels(op, map[string]string{v1alpha1.LabelCronOperationName: co.GetName()})
+
+	av, k := v1alpha1.CronOperationGroupVersionKind.ToAPIVersionAndKind()
+	meta.AddOwnerReference(op, meta.AsController(&xpv1.TypedReference{
+		APIVersion: av,
+		Kind:       k,
+		Name:       co.GetName(),
+		UID:        co.GetUID(),
+	}))
+
+	return op
+}
diff --git a/internal/controller/ops/operation/constructor.go b/internal/controller/ops/operation/constructor.go
index 474d417c8..39db538ca 100644
--- a/internal/controller/ops/operation/constructor.go
+++ b/internal/controller/ops/operation/constructor.go
@@ -84,6 +84,13 @@ func WithCapabilityChecker(cc xfn.CapabilityChecker) ReconcilerOption {
 	}
 }
 
+// WithRequiredResourcesFetcher specifies how the Reconciler should fetch required resources.
+func WithRequiredResourcesFetcher(f xfn.RequiredResourcesFetcher) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.resources = f
+	}
+}
+
 // NewReconciler returns a Reconciler of Usages.
 func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
@@ -92,6 +99,7 @@ func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 		record:     event.NewNopRecorder(),
 		conditions: conditions.ObservedGenerationPropagationManager{},
 		functions:  xfn.NewRevisionCapabilityChecker(mgr.GetClient()),
+		resources:  xfn.NewExistingRequiredResourcesFetcher(mgr.GetClient()),
 	}
 
 	for _, f := range opts {
diff --git a/internal/controller/ops/operation/reconciler.go b/internal/controller/ops/operation/reconciler.go
index a49c87912..535781e3c 100644
--- a/internal/controller/ops/operation/reconciler.go
+++ b/internal/controller/ops/operation/reconciler.go
@@ -53,12 +53,13 @@ const DefaultRetryLimit = 5
 
 // Event reasons.
 const (
-	reasonRunPipelineStep    = "RunPipelineStep"
-	reasonMaxFailures        = "MaxFailures"
-	reasonFunctionInvocation = "FunctionInvocation"
-	reasonInvalidOutput      = "InvalidOutput"
-	reasonInvalidResource    = "InvalidResource"
-	reasonInvalidPipeline    = "InvalidPipeline"
+	reasonRunPipelineStep       = "RunPipelineStep"
+	reasonMaxFailures           = "MaxFailures"
+	reasonFunctionInvocation    = "FunctionInvocation"
+	reasonInvalidOutput         = "InvalidOutput"
+	reasonInvalidResource       = "InvalidResource"
+	reasonInvalidPipeline       = "InvalidPipeline"
+	reasonBootstrapRequirements = "BootstrapRequirements"
 )
 
 // FieldOwnerPrefix is used to form the server-side apply field owner
@@ -75,6 +76,7 @@ type Reconciler struct {
 
 	pipeline  xfn.FunctionRunner
 	functions xfn.CapabilityChecker
+	resources xfn.RequiredResourcesFetcher
 }
 
 // Reconcile an Operation by running its function pipeline.
@@ -221,6 +223,28 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			}
 		}
 
+		// Pre-populate bootstrap requirements
+		if fn.Requirements != nil {
+			req.RequiredResources = map[string]*fnv1.Resources{}
+			for _, sel := range fn.Requirements.RequiredResources {
+				resources, err := r.resources.Fetch(ctx, ToProtobufResourceSelector(sel))
+				if err != nil {
+					op.Status.Failures++
+
+					log.Debug("Cannot fetch bootstrap required resources", "error", err, "failures", op.Status.Failures, "requirement", sel.RequirementName)
+					err = errors.Wrapf(err, "cannot fetch bootstrap required resources for requirement %q", sel.RequirementName)
+					r.record.Event(op, event.Warning(reasonBootstrapRequirements, err))
+					status.MarkConditions(xpv1.ReconcileError(err))
+					_ = r.client.Status().Update(ctx, op)
+
+					return reconcile.Result{}, err
+				}
+
+				// Add to request (resources could be nil if not found)
+				req.RequiredResources[sel.RequirementName] = resources
+			}
+		}
+
 		req.Meta = &fnv1.RequestMeta{Tag: xfn.Tag(req)}
 
 		rsp, err := r.pipeline.RunFunction(ctx, fn.FunctionRef.Name, req)
@@ -311,7 +335,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			op.Status.Failures++
 			log.Debug("Cannot apply desired resource", "error", err, "failures", op.Status.Failures, "resource-name", name)
 
-			err = errors.Wrap(err, "cannot load desired resource")
+			err = errors.Wrap(err, "cannot apply desired resource")
 			r.record.Event(op, event.Warning(reasonInvalidResource, err))
 			status.MarkConditions(xpv1.ReconcileError(err))
 			_ = r.client.Status().Update(ctx, op)
@@ -388,3 +412,30 @@ func AddPipelineStepOutput(pipeline []v1alpha1.PipelineStepStatus, step string,
 		Output: output,
 	})
 }
+
+// ToProtobufResourceSelector converts API RequiredResourceSelector to protobuf ResourceSelector.
+func ToProtobufResourceSelector(r v1alpha1.RequiredResourceSelector) *fnv1.ResourceSelector {
+	selector := &fnv1.ResourceSelector{
+		ApiVersion: r.APIVersion,
+		Kind:       r.Kind,
+		Namespace:  r.Namespace,
+	}
+
+	// You can only set one of name or matchLabels.
+	if r.Name != nil {
+		selector.Match = &fnv1.ResourceSelector_MatchName{
+			MatchName: *r.Name,
+		}
+		return selector
+	}
+
+	if len(r.MatchLabels) > 0 {
+		selector.Match = &fnv1.ResourceSelector_MatchLabels{
+			MatchLabels: &fnv1.MatchLabels{
+				Labels: r.MatchLabels,
+			},
+		}
+	}
+
+	return selector
+}
diff --git a/internal/controller/ops/ops.go b/internal/controller/ops/ops.go
index 4ecad2b85..11c933f82 100644
--- a/internal/controller/ops/ops.go
+++ b/internal/controller/ops/ops.go
@@ -21,10 +21,18 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 
 	"github.com/crossplane/crossplane/internal/controller/ops/controller"
+	"github.com/crossplane/crossplane/internal/controller/ops/cronoperation"
 	"github.com/crossplane/crossplane/internal/controller/ops/operation"
+	"github.com/crossplane/crossplane/internal/controller/ops/watchoperation"
 )
 
 // Setup API extensions controllers.
 func Setup(mgr ctrl.Manager, o controller.Options) error {
-	return operation.Setup(mgr, o)
+	if err := operation.Setup(mgr, o); err != nil {
+		return err
+	}
+	if err := cronoperation.Setup(mgr, o); err != nil {
+		return err
+	}
+	return watchoperation.Setup(mgr, o)
 }
diff --git a/internal/controller/ops/watched/constructor.go b/internal/controller/ops/watched/constructor.go
new file mode 100644
index 000000000..e62d6b9e3
--- /dev/null
+++ b/internal/controller/ops/watched/constructor.go
@@ -0,0 +1,63 @@
+/*
+Copyright 2025 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 watched implements a controller for resources watched by WatchOperations.
+package watched
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+)
+
+// ReconcilerOption is used to configure the Reconciler.
+type ReconcilerOption func(*Reconciler)
+
+// WithLogger specifies how the Reconciler should log messages.
+func WithLogger(log logging.Logger) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.log = log
+	}
+}
+
+// WithRecorder specifies how the Reconciler should record events.
+func WithRecorder(er event.Recorder) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.record = er
+	}
+}
+
+// NewReconciler returns a Reconciler that watches resources on behalf of
+// a WatchOperation.
+func NewReconciler(c client.Client, wo *v1alpha1.WatchOperation, opts ...ReconcilerOption) *Reconciler {
+	r := &Reconciler{
+		client:      c,
+		watchOpName: wo.GetName(),
+		watchedGVK:  schema.FromAPIVersionAndKind(wo.Spec.Watch.APIVersion, wo.Spec.Watch.Kind),
+		log:         logging.NewNopLogger(),
+		record:      event.NewNopRecorder(),
+	}
+
+	for _, f := range opts {
+		f(r)
+	}
+
+	return r
+}
diff --git a/internal/controller/ops/watched/reconciler.go b/internal/controller/ops/watched/reconciler.go
new file mode 100644
index 000000000..6eeea811d
--- /dev/null
+++ b/internal/controller/ops/watched/reconciler.go
@@ -0,0 +1,285 @@
+/*
+Copyright 2025 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 watched implements a controller for resources watched by WatchOperations.
+package watched
+
+import (
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"time"
+
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"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/resource"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	"github.com/crossplane/crossplane/internal/ops/lifecycle"
+)
+
+const (
+	timeout = 2 * time.Minute
+)
+
+// Event reasons.
+const (
+	reasonListOperations          event.Reason = "ListOperations"
+	reasonReplaceRunningOperation event.Reason = "ReplaceRunningOperation"
+	reasonCreateOperation         event.Reason = "CreateOperation"
+	reasonWatchOperationGet       event.Reason = "GetWatchOperation"
+)
+
+// A Reconciler reconciles watched resources by creating Operations
+// when the watched resources change.
+type Reconciler struct {
+	client client.Client
+	log    logging.Logger
+	record event.Recorder
+
+	watchOpName string
+	watchedGVK  schema.GroupVersionKind
+}
+
+// Reconcile is triggered when a watched resource changes, and creates an
+// Operation using the WatchOperation's template.
+func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
+	log := r.log.WithValues(
+		"request", req,
+		"gvk", r.watchedGVK,
+	)
+	log.Debug("Reconciling watched resource")
+
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+
+	// Get the watched resource that triggered this reconcile.
+	watched := &unstructured.Unstructured{}
+	watched.SetGroupVersionKind(r.watchedGVK)
+	if err := r.client.Get(ctx, req.NamespacedName, watched); err != nil {
+		if !kerrors.IsNotFound(err) {
+			log.Debug("Cannot get watched resource", "error", err)
+			return reconcile.Result{}, errors.Wrap(err, "cannot get watched resource")
+		}
+
+		// NOTE(negz): The underlying informer has the final snapshot of
+		// the object when it was deleted, but controller-runtime does
+		// not expose it to us (by design).
+		//
+		// In this case I'd rather have the final snapshot, but not so
+		// much that I want to fight controller-runtime about it. The
+		// only downside of using this synthetic resource is that if we
+		// see the same delete event multiple times we'll create
+		// multiple Operations (due to the different synthetic deletion
+		// timestamps). This should be unlikely as the work queue tries
+		// to dedupe
+		//
+		// We could also force access to the deleted event by adding a
+		// finalizer to all watched resources, but that's way too
+		// invasive for my taste.
+
+		log.Debug("Watched resource was deleted, using synthetic resource to process deletion event")
+
+		watched.SetName(req.Name)
+		watched.SetNamespace(req.Namespace)
+		watched.SetDeletionTimestamp(ptr.To(metav1.Now()))
+		watched.SetResourceVersion(v1alpha1.SyntheticResourceVersionDeleted)
+	}
+
+	log = log.WithValues(
+		"uid", watched.GetUID(),
+		"version", watched.GetResourceVersion(),
+		"namespace", watched.GetNamespace(),
+		"name", watched.GetName(),
+	)
+
+	// Get the current WatchOperation to ensure it still exists and get latest spec.
+	wo := &v1alpha1.WatchOperation{}
+	if err := r.client.Get(ctx, client.ObjectKey{Name: r.watchOpName}, wo); err != nil {
+		log.Debug("Cannot get WatchOperation", "error", err)
+		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), "cannot get WatchOperation")
+	}
+
+	// Don't reconcile if the WatchOperation is paused.
+	if meta.IsPaused(wo) {
+		log.Debug("WatchOperation is paused")
+		return reconcile.Result{Requeue: false}, nil
+	}
+
+	// Don't reconcile if the WatchOperation is being deleted.
+	if meta.WasDeleted(wo) {
+		log.Debug("WatchOperation is being deleted")
+		return reconcile.Result{Requeue: false}, nil
+	}
+
+	// List existing Operations for this WatchOperation.
+	ol := &v1alpha1.OperationList{}
+	if err := r.client.List(ctx, ol, client.MatchingLabels{v1alpha1.LabelWatchOperationName: wo.GetName()}); err != nil {
+		log.Debug("Cannot list Operations", "error", err)
+		err = errors.Wrap(err, "cannot list Operations")
+		r.record.Event(wo, event.Warning(reasonListOperations, err))
+		return reconcile.Result{}, err
+	}
+
+	// Record all running Operations.
+	running := make(map[string]bool)
+	for _, op := range lifecycle.WithReason(v1alpha1.ReasonPipelineRunning, ol.Items...) {
+		running[op.GetName()] = true
+	}
+
+	// Check concurrency policy before creating new Operations.
+	if len(running) > 0 {
+		switch p := ptr.Deref(wo.Spec.ConcurrencyPolicy, v1alpha1.ConcurrencyPolicyAllow); p {
+		case v1alpha1.ConcurrencyPolicyAllow:
+			log.Debug("Concurrency policy allows creating Operations while other Operations are running", "policy", p, "running", len(running))
+		case v1alpha1.ConcurrencyPolicyForbid:
+			log.Debug("Concurrency policy forbids creating Operations while other Operations are running", "policy", p, "running", len(running))
+			return reconcile.Result{Requeue: false}, nil
+		case v1alpha1.ConcurrencyPolicyReplace:
+			log.Debug("Concurrency policy requires deleting other running Operations", "policy", p, "running", len(running))
+			for _, op := range ol.Items {
+				if !running[op.GetName()] {
+					continue
+				}
+				if err := r.client.Delete(ctx, &op); resource.IgnoreNotFound(err) != nil {
+					log.Debug("Cannot delete running Operation", "error", err, "operation", op.GetName())
+					err = errors.Wrapf(err, "cannot delete running Operation %q", op.GetName())
+					r.record.Event(wo, event.Warning(reasonReplaceRunningOperation, err))
+					return reconcile.Result{}, err
+				}
+				log.Debug("Deleted running operation due to concurrency policy", "policy", p, "operation", op.GetName())
+			}
+		}
+	}
+
+	// Generate a unique name for the Operation.
+	name := OperationName(wo, watched)
+
+	// Check if we've already created an Operation for this resource version.
+	for _, op := range ol.Items {
+		if op.GetName() == name {
+			log.Debug("Operation already exists for this resource version", "operation", name)
+			return reconcile.Result{}, nil
+		}
+	}
+
+	// Create the Operation.
+	op := NewOperation(wo, watched, name)
+	if err := r.client.Create(ctx, op); err != nil {
+		log.Debug("Cannot create Operation", "error", err, "operation", op.GetName())
+		err = errors.Wrapf(err, "cannot create Operation %q", op.GetName())
+		r.record.Event(wo, event.Warning(reasonCreateOperation, err))
+		return reconcile.Result{}, err
+	}
+
+	log.Debug("Created Operation for watched resource", "operation", op.GetName(), "resource", watched.GetName())
+	return reconcile.Result{}, nil
+}
+
+// OperationName generates a deterministic and unique name for an Operation
+// based on the WatchOperation name and a hash of the watched resource's GVK,
+// namespace, name, UID, resource version, and deletion timestamp.
+func OperationName(wo *v1alpha1.WatchOperation, watched *unstructured.Unstructured) string {
+	in := watched.GroupVersionKind().String() + "/" +
+		watched.GetNamespace() + "/" +
+		watched.GetName() + "/" +
+		string(watched.GetUID()) + "/" +
+		watched.GetResourceVersion()
+
+	// For synthetic deletion events, a unique deletion timestamp is set to
+	// ensure different resource instances (even with the same
+	// name/namespace) create different Operation names.
+	if t := watched.GetDeletionTimestamp(); t != nil {
+		in = in + "/" + t.String()
+	}
+
+	hash := sha256.Sum256([]byte(in))
+	return wo.GetName() + "-" + hex.EncodeToString(hash[:])[:7]
+}
+
+// NewOperation creates a new Operation using the WatchOperation's template,
+// injecting the watched resource into all pipeline steps.
+func NewOperation(wo *v1alpha1.WatchOperation, watched *unstructured.Unstructured, name string) *v1alpha1.Operation {
+	// Deep copy the spec to avoid mutating the original template
+	spec := wo.Spec.OperationTemplate.Spec.DeepCopy()
+
+	sel := v1alpha1.RequiredResourceSelector{
+		RequirementName: v1alpha1.RequirementNameWatchedResource,
+		APIVersion:      watched.GetAPIVersion(),
+		Kind:            watched.GetKind(),
+		Name:            ptr.To(watched.GetName()),
+	}
+
+	// Add namespace if the resource is namespaced
+	if watched.GetNamespace() != "" {
+		sel.Namespace = ptr.To(watched.GetNamespace())
+	}
+
+	// Inject the watched resource into each pipeline step
+	for i := range spec.Pipeline {
+		step := &spec.Pipeline[i]
+
+		if step.Requirements == nil {
+			step.Requirements = &v1alpha1.FunctionRequirements{}
+		}
+
+		step.Requirements.RequiredResources = append(step.Requirements.RequiredResources, sel)
+	}
+
+	op := &v1alpha1.Operation{
+		ObjectMeta: wo.Spec.OperationTemplate.ObjectMeta,
+		Spec:       *spec,
+	}
+
+	op.SetName(name)
+	meta.AddLabels(op, map[string]string{v1alpha1.LabelWatchOperationName: wo.GetName()})
+
+	// Add annotations with information about the watched resource
+	annotations := map[string]string{
+		v1alpha1.AnnotationWatchedResourceAPIVersion:      watched.GetAPIVersion(),
+		v1alpha1.AnnotationWatchedResourceKind:            watched.GetKind(),
+		v1alpha1.AnnotationWatchedResourceName:            watched.GetName(),
+		v1alpha1.AnnotationWatchedResourceResourceVersion: watched.GetResourceVersion(),
+	}
+
+	// Add namespace annotation if the resource is namespaced
+	if watched.GetNamespace() != "" {
+		annotations[v1alpha1.AnnotationWatchedResourceNamespace] = watched.GetNamespace()
+	}
+
+	meta.AddAnnotations(op, annotations)
+
+	av, k := v1alpha1.WatchOperationGroupVersionKind.ToAPIVersionAndKind()
+	meta.AddOwnerReference(op, meta.AsController(&xpv1.TypedReference{
+		APIVersion: av,
+		Kind:       k,
+		Name:       wo.GetName(),
+		UID:        wo.GetUID(),
+	}))
+
+	return op
+}
diff --git a/internal/controller/ops/watchoperation/constructor.go b/internal/controller/ops/watchoperation/constructor.go
new file mode 100644
index 000000000..1106158a4
--- /dev/null
+++ b/internal/controller/ops/watchoperation/constructor.go
@@ -0,0 +1,164 @@
+/*
+Copyright 2025 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 watchoperation manages the lifecycle of Watched controllers.
+package watchoperation
+
+import (
+	"context"
+	"strings"
+
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"github.com/crossplane/crossplane-runtime/pkg/controller"
+	"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/ratelimiter"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	opscontroller "github.com/crossplane/crossplane/internal/controller/ops/controller"
+	"github.com/crossplane/crossplane/internal/engine"
+)
+
+// 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
+	GetWatches(name string) ([]engine.WatchID, error)
+	StartWatches(ctx context.Context, name string, ws ...engine.Watch) error
+	StopWatches(ctx context.Context, name string, ws ...engine.WatchID) (int, error)
+	GetCached() client.Client
+	GetUncached() 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(_ context.Context, _ string, _ ...engine.Watch) error { return nil }
+
+// StopWatches does nothing.
+func (e *NopEngine) StopWatches(_ context.Context, _ string, _ ...engine.WatchID) (int, error) {
+	return 0, nil
+}
+
+// GetCached returns a nil client.
+func (e *NopEngine) GetCached() client.Client {
+	return nil
+}
+
+// GetUncached returns a nil client.
+func (e *NopEngine) GetUncached() client.Client {
+	return nil
+}
+
+// GetFieldIndexer returns a nil field indexer.
+func (e *NopEngine) GetFieldIndexer() client.FieldIndexer {
+	return nil
+}
+
+// Setup adds a controller that reconciles WatchOperations by starting a
+// controller to watch the specified resource type.
+func Setup(mgr ctrl.Manager, o opscontroller.Options) error {
+	name := "watchoperation/" + strings.ToLower(v1alpha1.WatchOperationGroupKind)
+
+	r := NewReconciler(mgr.GetClient(),
+		WithLogger(o.Logger.WithValues("controller", name)),
+		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
+		WithControllerEngine(o.ControllerEngine),
+		WithOptions(o))
+
+	return ctrl.NewControllerManagedBy(mgr).
+		Named(name).
+		For(&v1alpha1.WatchOperation{}).
+		Owns(&v1alpha1.Operation{}).
+		WithOptions(o.ForControllerRuntime()).
+		Complete(ratelimiter.NewReconciler(name, errors.WithSilentRequeueOnConflict(r), o.GlobalRateLimiter))
+}
+
+// ReconcilerOption is used to configure the Reconciler.
+type ReconcilerOption func(*Reconciler)
+
+// WithLogger specifies how the Reconciler should log messages.
+func WithLogger(log logging.Logger) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.log = log
+	}
+}
+
+// WithRecorder specifies how the Reconciler should record Kubernetes events.
+func WithRecorder(er event.Recorder) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.record = er
+	}
+}
+
+// WithControllerEngine specifies how the Reconciler should manage controllers.
+func WithControllerEngine(ce *engine.ControllerEngine) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.engine = ce
+	}
+}
+
+// WithOptions specifies how the Reconciler should configure controllers.
+func WithOptions(o opscontroller.Options) ReconcilerOption {
+	return func(r *Reconciler) {
+		r.options = o
+	}
+}
+
+// NewReconciler returns a Reconciler of WatchOperations.
+func NewReconciler(c client.Client, opts ...ReconcilerOption) *Reconciler {
+	r := &Reconciler{
+		client:     c,
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
+		finalizer:  resource.NewAPIFinalizer(c, finalizer),
+		engine:     &NopEngine{},
+		options: opscontroller.Options{
+			Options: controller.DefaultOptions(),
+		},
+	}
+
+	for _, f := range opts {
+		f(r)
+	}
+
+	return r
+}
diff --git a/internal/controller/ops/watchoperation/reconciler.go b/internal/controller/ops/watchoperation/reconciler.go
new file mode 100644
index 000000000..134cc726a
--- /dev/null
+++ b/internal/controller/ops/watchoperation/reconciler.go
@@ -0,0 +1,233 @@
+/*
+Copyright 2025 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 watchoperation manages the lifecycle of Watched controllers.
+package watchoperation
+
+import (
+	"context"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/ratelimiter"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	opscontroller "github.com/crossplane/crossplane/internal/controller/ops/controller"
+	"github.com/crossplane/crossplane/internal/controller/ops/watched"
+	"github.com/crossplane/crossplane/internal/engine"
+	"github.com/crossplane/crossplane/internal/ops/lifecycle"
+)
+
+const (
+	timeout   = 2 * time.Minute
+	finalizer = "watchoperation.ops.crossplane.io"
+)
+
+// Event reasons.
+const (
+	reasonEstablishWatched event.Reason = "EstablishWatched"
+	reasonTerminateWatched event.Reason = "TerminateWatched"
+	reasonGarbageCollect   event.Reason = "GarbageCollectOperations"
+)
+
+// WatchTypeWatchOperation is the watch type used when a WatchOperation
+// controller watches resources specified in the WatchOperation spec.
+const WatchTypeWatchOperation engine.WatchType = "WatchOperation"
+
+// A Reconciler reconciles WatchOperations.
+type Reconciler struct {
+	client client.Client
+	log    logging.Logger
+	record event.Recorder
+
+	finalizer  resource.Finalizer
+	conditions conditions.Manager
+
+	engine  ControllerEngine
+	options opscontroller.Options
+}
+
+// Reconcile a WatchOperation by starting a controller to watch the specified
+// resource type and create Operations when those resources change.
+func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
+	log := r.log.WithValues("request", req)
+	log.Debug("Reconciling")
+
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+
+	wo := &v1alpha1.WatchOperation{}
+	if err := r.client.Get(ctx, req.NamespacedName, wo); err != nil {
+		log.Debug("Cannot get WatchOperation", "error", err)
+		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), "cannot get WatchOperation")
+	}
+
+	status := r.conditions.For(wo)
+
+	log = log.WithValues(
+		"uid", wo.GetUID(),
+		"version", wo.GetResourceVersion(),
+		"name", wo.GetName(),
+		"watched-gvk", schema.FromAPIVersionAndKind(wo.Spec.Watch.APIVersion, wo.Spec.Watch.Kind),
+	)
+
+	if meta.WasDeleted(wo) {
+		if err := r.engine.Stop(ctx, WatchedControllerName(wo.GetName())); err != nil {
+			log.Debug("Cannot stop watched resource controller", "error", err)
+			err = errors.Wrap(err, "cannot stop watched resource controller")
+			r.record.Event(wo, event.Warning(reasonTerminateWatched, err))
+			status.MarkConditions(xpv1.ReconcileError(err))
+			_ = r.client.Status().Update(ctx, wo)
+			return reconcile.Result{}, err
+		}
+
+		log.Debug("Stopped watched resource controller")
+
+		if err := r.finalizer.RemoveFinalizer(ctx, wo); err != nil {
+			log.Debug("Cannot remove watched resource finalizer", "error", err)
+			err = errors.Wrap(err, "cannot remove watched resource finalizer")
+			r.record.Event(wo, event.Warning(reasonTerminateWatched, err))
+			status.MarkConditions(xpv1.ReconcileError(err))
+			_ = r.client.Status().Update(ctx, wo)
+			return reconcile.Result{}, err
+		}
+
+		return reconcile.Result{Requeue: false}, nil
+	}
+
+	if err := r.finalizer.AddFinalizer(ctx, wo); err != nil {
+		log.Debug("Cannot add watched resource finalizer", "error", err)
+		err = errors.Wrap(err, "cannot add watched resource finalizer")
+		r.record.Event(wo, event.Warning(reasonEstablishWatched, err))
+		status.MarkConditions(xpv1.ReconcileError(err))
+		_ = r.client.Status().Update(ctx, wo)
+		return reconcile.Result{}, err
+	}
+
+	// Don't reconcile if the WatchOperation is paused.
+	if meta.IsPaused(wo) {
+		log.Debug("WatchOperation is paused")
+		status.MarkConditions(v1alpha1.WatchPaused(), xpv1.ReconcilePaused())
+		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, wo), "cannot update status of WatchOperation")
+	}
+
+	// List Operations to update status.
+	ol := &v1alpha1.OperationList{}
+	if err := r.client.List(ctx, ol, client.MatchingLabels{v1alpha1.LabelWatchOperationName: wo.GetName()}); err != nil {
+		log.Debug("Cannot list Operations", "error", err)
+		err = errors.Wrap(err, "cannot list Operations")
+		r.record.Event(wo, event.Warning(reasonEstablishWatched, err))
+		status.MarkConditions(xpv1.ReconcileError(err))
+		_ = r.client.Status().Update(ctx, wo)
+		return reconcile.Result{}, err
+	}
+
+	// Update LastScheduleTime from the latest Operation creation time.
+	if t := lifecycle.LatestCreateTime(ol.Items...); !t.IsZero() {
+		wo.Status.LastScheduleTime = &metav1.Time{Time: t}
+	}
+
+	// Update LastSuccessfulTime from the latest successful Operation.
+	if t := lifecycle.LatestSucceededTransitionTime(lifecycle.WithReason(v1alpha1.ReasonPipelineSuccess, ol.Items...)...); !t.IsZero() {
+		wo.Status.LastSuccessfulTime = &metav1.Time{Time: t}
+	}
+
+	// Count resources being watched. This is best effort. If we hit an
+	// error we just don't update it this time around.
+	ul := &unstructured.UnstructuredList{}
+	ul.SetGroupVersionKind(schema.FromAPIVersionAndKind(wo.Spec.Watch.APIVersion, wo.Spec.Watch.Kind))
+	if err := r.client.List(ctx, ul, client.InNamespace(wo.Spec.Watch.Namespace), client.MatchingLabels(wo.Spec.Watch.MatchLabels)); err == nil {
+		wo.Status.WatchingResources = int64(len(ul.Items))
+	}
+
+	// Garbage collect Operations older than the history limits.
+	for _, op := range lifecycle.MarkGarbage(ptr.Deref(wo.Spec.SuccessfulHistoryLimit, 3), ptr.Deref(wo.Spec.FailedHistoryLimit, 1), ol.Items...) {
+		if err := r.client.Delete(ctx, &op); resource.IgnoreNotFound(err) != nil {
+			log.Debug("Cannot garbage collect Operation", "error", err, "operation", op.GetName())
+			err = errors.Wrapf(err, "cannot garbage collect Operation %q", op.GetName())
+			r.record.Event(wo, event.Warning(reasonGarbageCollect, err))
+			status.MarkConditions(xpv1.ReconcileError(err))
+			_ = r.client.Status().Update(ctx, wo)
+			return reconcile.Result{}, err
+		}
+	}
+
+	// Update status with running operations.
+	running := make([]string, 0)
+	for _, op := range ol.Items {
+		if op.GetCondition(v1alpha1.TypeSucceeded).Reason == v1alpha1.ReasonPipelineRunning {
+			running = append(running, op.GetName())
+		}
+	}
+	wo.Status.RunningOperationRefs = lifecycle.RunningOperationRefs(running)
+
+	// Start the Watched controller.
+	wr := watched.NewReconciler(r.engine.GetCached(), wo,
+		watched.WithLogger(r.log.WithValues("controller", WatchedControllerName(wo.GetName()))),
+		watched.WithRecorder(r.record.WithAnnotations("controller", WatchedControllerName(wo.GetName()))))
+
+	ko := r.options.ForControllerRuntime()
+	ko.Reconciler = ratelimiter.NewReconciler(WatchedControllerName(wo.GetName()), errors.WithSilentRequeueOnConflict(wr), r.options.GlobalRateLimiter)
+
+	name := WatchedControllerName(wo.GetName())
+	co := []engine.ControllerOption{engine.WithRuntimeOptions(ko)}
+
+	if err := r.engine.Start(name, co...); err != nil {
+		log.Debug("Cannot start watched resource controller", "error", err)
+		err = errors.Wrap(err, "cannot start watched resource controller")
+		r.record.Event(wo, event.Warning(reasonEstablishWatched, err))
+		status.MarkConditions(v1alpha1.WatchFailed(err.Error()), xpv1.ReconcileError(err))
+		_ = r.client.Status().Update(ctx, wo)
+		return reconcile.Result{}, err
+	}
+
+	// Start watching the specified kind of resource.
+	u := &unstructured.Unstructured{}
+	u.SetGroupVersionKind(schema.FromAPIVersionAndKind(wo.Spec.Watch.APIVersion, wo.Spec.Watch.Kind))
+
+	if err := r.engine.StartWatches(ctx, name, engine.WatchFor(u, WatchTypeWatchOperation, NewWatchedResourceHandler(wo))); err != nil {
+		log.Debug("Cannot start watched resource controller watches", "error", err)
+		err = errors.Wrap(err, "cannot start watched resource controller watches")
+		r.record.Event(wo, event.Warning(reasonEstablishWatched, err))
+		status.MarkConditions(v1alpha1.WatchFailed(err.Error()), xpv1.ReconcileError(err))
+		_ = r.client.Status().Update(ctx, wo)
+		return reconcile.Result{}, err
+	}
+
+	log.Debug("Started watched resource controller")
+
+	status.MarkConditions(v1alpha1.WatchActive(), xpv1.ReconcileSuccess())
+	return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, wo), "cannot update status of WatchOperation")
+}
+
+// WatchedControllerName returns the recommended name for controllers that watch
+// resources on behalf of a WatchOperation.
+func WatchedControllerName(name string) string {
+	return "watched/" + name
+}
diff --git a/internal/controller/ops/watchoperation/watch.go b/internal/controller/ops/watchoperation/watch.go
new file mode 100644
index 000000000..d3fc9e7f4
--- /dev/null
+++ b/internal/controller/ops/watchoperation/watch.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2025 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 watchoperation
+
+import (
+	"context"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+)
+
+// NewWatchedResourceHandler returns a handler that enqueues reconcile requests
+// for the WatchOperation when watched resources change, filtering based on the
+// WatchOperation's matchLabels and namespace specifications.
+func NewWatchedResourceHandler(wo *v1alpha1.WatchOperation) handler.EventHandler {
+	return handler.EnqueueRequestsFromMapFunc(func(_ context.Context, obj client.Object) []reconcile.Request {
+		// Convert to unstructured to handle any resource type
+		u, ok := obj.(*unstructured.Unstructured)
+		if !ok {
+			return nil
+		}
+
+		// Apply namespace filtering if specified
+		if wo.Spec.Watch.Namespace != "" {
+			// For cluster-scoped resources, namespace filtering doesn't apply
+			if u.GetNamespace() != "" && u.GetNamespace() != wo.Spec.Watch.Namespace {
+				return nil
+			}
+		}
+
+		// Apply label selector filtering if specified
+		if len(wo.Spec.Watch.MatchLabels) > 0 {
+			selector := labels.SelectorFromSet(wo.Spec.Watch.MatchLabels)
+			if !selector.Matches(labels.Set(u.GetLabels())) {
+				return nil
+			}
+		}
+
+		// Resource matches filters, enqueue the watched resource for reconciliation
+		return []reconcile.Request{
+			{NamespacedName: types.NamespacedName{Name: u.GetName(), Namespace: u.GetNamespace()}},
+		}
+	})
+}
diff --git a/internal/controller/pkg/revision/establisher.go b/internal/controller/pkg/revision/establisher.go
index 4a93f2a83..e15e44650 100644
--- a/internal/controller/pkg/revision/establisher.go
+++ b/internal/controller/pkg/revision/establisher.go
@@ -38,6 +38,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 )
 
@@ -394,6 +395,29 @@ func (e *APIEstablisher) enrichControlledResource(res runtime.Object, webhookTLS
 			conf.Spec.Conversion.Webhook.ClientConfig.Service.Namespace = e.namespace
 			conf.Spec.Conversion.Webhook.ClientConfig.Service.Port = ptr.To[int32](ServicePort)
 		}
+	case *v2alpha1.ManagedResourceDefinition:
+		if conf.Spec.Conversion != nil && conf.Spec.Conversion.Strategy == extv1.WebhookConverter {
+			if len(webhookTLSCert) == 0 {
+				return errors.New(errConversionWithNoWebhookCA)
+			}
+
+			if conf.Spec.Conversion.Webhook == nil {
+				conf.Spec.Conversion.Webhook = &extv1.WebhookConversion{}
+			}
+
+			if conf.Spec.Conversion.Webhook.ClientConfig == nil {
+				conf.Spec.Conversion.Webhook.ClientConfig = &extv1.WebhookClientConfig{}
+			}
+
+			if conf.Spec.Conversion.Webhook.ClientConfig.Service == nil {
+				conf.Spec.Conversion.Webhook.ClientConfig.Service = &extv1.ServiceReference{}
+			}
+
+			conf.Spec.Conversion.Webhook.ClientConfig.CABundle = webhookTLSCert
+			conf.Spec.Conversion.Webhook.ClientConfig.Service.Name = parent.GetLabels()[v1.LabelParentPackage]
+			conf.Spec.Conversion.Webhook.ClientConfig.Service.Namespace = e.namespace
+			conf.Spec.Conversion.Webhook.ClientConfig.Service.Port = ptr.To[int32](ServicePort)
+		}
 	}
 
 	return nil
diff --git a/internal/controller/pkg/revision/reconciler.go b/internal/controller/pkg/revision/reconciler.go
index bd3e9958d..2a84a7824 100644
--- a/internal/controller/pkg/revision/reconciler.go
+++ b/internal/controller/pkg/revision/reconciler.go
@@ -48,6 +48,7 @@ import (
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 	"github.com/crossplane/crossplane/apis/pkg/v1beta1"
 	"github.com/crossplane/crossplane/internal/controller/pkg/controller"
+	"github.com/crossplane/crossplane/internal/converter"
 	"github.com/crossplane/crossplane/internal/dag"
 	"github.com/crossplane/crossplane/internal/features"
 	"github.com/crossplane/crossplane/internal/version"
@@ -109,6 +110,7 @@ const (
 	reasonParse        event.Reason = "ParsePackage"
 	reasonLint         event.Reason = "LintPackage"
 	reasonDependencies event.Reason = "ResolveDependencies"
+	reasonConvertCRD   event.Reason = "ConvertCRDToMRD"
 	reasonSync         event.Reason = "SyncPackage"
 	reasonDeactivate   event.Reason = "DeactivateRevision"
 	reasonPaused       event.Reason = "ReconciliationPaused"
@@ -874,8 +876,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 	}
 
+	objects := pkg.GetObjects()
+	// The CustomToManagedResourceConversion feature converts CRDs to MRDs from
+	// a provider package.
+	if _, ok := pkgMeta.(*pkgmetav1.Provider); ok && r.features.Enabled(features.EnableBetaCustomToManagedResourceConversion) {
+		// Convert CRDs to MRDs
+		// If SafeStart is not in capabilities, we default mrd state to Active.
+		activationState := !pkgmetav1.CapabilitiesContainFuzzyMatch(pr.GetCapabilities(), pkgmetav1.ProviderCapabilitySafeStart)
+		if mrdObjs, err := converter.CustomToManagedResourceDefinitions(activationState, objects...); err != nil {
+			log.Debug("failed to convert CRDs to MRDs for provider, skipping conversion", "error", err)
+			r.record.Event(pr, event.Warning(reasonConvertCRD, err))
+		} else {
+			objects = mrdObjs
+		}
+	}
+
 	// Establish control or ownership of objects.
-	refs, err := r.objects.Establish(ctx, pkg.GetObjects(), pr, pr.GetDesiredState() == v1.PackageRevisionActive)
+	refs, err := r.objects.Establish(ctx, objects, pr, pr.GetDesiredState() == v1.PackageRevisionActive)
 	if err != nil {
 		if kerrors.IsConflict(err) {
 			return reconcile.Result{Requeue: true}, nil
diff --git a/internal/controller/pkg/runtime/migration.go b/internal/controller/pkg/runtime/migration.go
index 4bca75d46..19e9d2de5 100644
--- a/internal/controller/pkg/runtime/migration.go
+++ b/internal/controller/pkg/runtime/migration.go
@@ -115,7 +115,7 @@ func (m *DeletingDeploymentSelectorMigrator) MigrateDeploymentSelector(ctx conte
 		return nil
 	}
 
-	expected := existingDeploy.Spec.Selector.MatchLabels["pkg.crossplane.io/provider"]
+	expected := existingDeploy.Spec.Selector.MatchLabels[v1.LabelProvider]
 
 	// Check if the provider label needs migration
 	if expected == existing {
diff --git a/internal/controller/pkg/runtime/runtime.go b/internal/controller/pkg/runtime/runtime.go
index 8fa1e49ef..a2f6adcf7 100644
--- a/internal/controller/pkg/runtime/runtime.go
+++ b/internal/controller/pkg/runtime/runtime.go
@@ -331,7 +331,7 @@ func (b *DeploymentRuntimeBuilder) TLSServerSecret() *corev1.Secret {
 
 func (b *DeploymentRuntimeBuilder) podSelectors() map[string]string {
 	return map[string]string{
-		"pkg.crossplane.io/revision":           b.revision.GetName(),
+		v1.LabelRevision:                       b.revision.GetName(),
 		"pkg.crossplane.io/" + b.packageType(): b.packageName(),
 	}
 }
diff --git a/internal/controller/pkg/runtime/runtime_function.go b/internal/controller/pkg/runtime/runtime_function.go
index 87298820e..70a6c6f5c 100644
--- a/internal/controller/pkg/runtime/runtime_function.go
+++ b/internal/controller/pkg/runtime/runtime_function.go
@@ -130,7 +130,7 @@ func (h *FunctionHooks) Post(ctx context.Context, pr v1.PackageRevisionWithRunti
 		return errors.Wrap(err, errParseFunctionImage)
 	}
 
-	d := build.Deployment(sa.Name, functionDeploymentOverrides(image.Name())...)
+	d := build.Deployment(sa.Name, functionDeploymentOverrides(pr, image.Name())...)
 	// Create/Apply the SA only if the deployment references it.
 	// This is to avoid creating a SA that is NOT used by the deployment when
 	// the SA is managed externally by the user and configured by setting
@@ -182,7 +182,7 @@ func (h *FunctionHooks) Deactivate(ctx context.Context, _ v1.PackageRevisionWith
 	return nil
 }
 
-func functionDeploymentOverrides(image string) []DeploymentOverride {
+func functionDeploymentOverrides(pr v1.PackageRevisionWithRuntime, image string) []DeploymentOverride {
 	do := []DeploymentOverride{
 		DeploymentRuntimeWithAdditionalPorts([]corev1.ContainerPort{
 			{
@@ -190,6 +190,28 @@ func functionDeploymentOverrides(image string) []DeploymentOverride {
 				ContainerPort: GRPCPort,
 			},
 		}),
+		DeploymentRuntimeWithAdditionalEnvironments([]corev1.EnvVar{
+			{
+				Name: "FUNCTION_NAME",
+				ValueFrom: &corev1.EnvVarSource{
+					FieldRef: &corev1.ObjectFieldSelector{
+						FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelFunction),
+					},
+				},
+			},
+			{
+				Name: "REVISION_NAME",
+				ValueFrom: &corev1.EnvVarSource{
+					FieldRef: &corev1.ObjectFieldSelector{
+						FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelRevision),
+					},
+				},
+			},
+			{
+				Name:  "REVISION_UID",
+				Value: string(pr.GetUID()),
+			},
+		}),
 	}
 
 	do = append(do, DeploymentRuntimeWithOptionalImage(image))
diff --git a/internal/controller/pkg/runtime/runtime_provider.go b/internal/controller/pkg/runtime/runtime_provider.go
index 18ca67202..532d8e1f1 100644
--- a/internal/controller/pkg/runtime/runtime_provider.go
+++ b/internal/controller/pkg/runtime/runtime_provider.go
@@ -205,6 +205,26 @@ func providerDeploymentOverrides(pr v1.PackageRevisionWithRuntime, image string)
 					},
 				},
 			},
+			{
+				Name: "PROVIDER_NAME",
+				ValueFrom: &corev1.EnvVarSource{
+					FieldRef: &corev1.ObjectFieldSelector{
+						FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelProvider),
+					},
+				},
+			},
+			{
+				Name: "REVISION_NAME",
+				ValueFrom: &corev1.EnvVarSource{
+					FieldRef: &corev1.ObjectFieldSelector{
+						FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelRevision),
+					},
+				},
+			},
+			{
+				Name:  "REVISION_UID",
+				Value: string(pr.GetUID()),
+			},
 		}),
 
 		// Add optional scrape annotations to the deployment. It is possible to
diff --git a/internal/controller/rbac/provider/roles/reconciler.go b/internal/controller/rbac/provider/roles/reconciler.go
index 7a2619859..3c525b1b5 100644
--- a/internal/controller/rbac/provider/roles/reconciler.go
+++ b/internal/controller/rbac/provider/roles/reconciler.go
@@ -43,6 +43,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 	"github.com/crossplane/crossplane/internal/controller/rbac/controller"
 )
@@ -330,8 +331,14 @@ func DefinedResources(refs []xpv1.TypedReference) []Resource {
 		// just skip this resource since it can't be a CRD.
 		gv, _ := schema.ParseGroupVersion(ref.APIVersion)
 
-		// We're only concerned with CRDs.
-		if gv.Group != apiextensions.GroupName || ref.Kind != "CustomResourceDefinition" {
+		// We're only concerned with CRDs or MRDs.
+		switch {
+		case gv.Group == apiextensions.GroupName && ref.Kind == "CustomResourceDefinition":
+		// Do the work!
+		case gv.Group == v2alpha1.Group && ref.Kind == v2alpha1.ManagedResourceDefinitionKind:
+		// Do the work!
+		default:
+			// Filter out the non CRD or MRD.
 			continue
 		}
 
diff --git a/internal/controller/rbac/provider/roles/roles.go b/internal/controller/rbac/provider/roles/roles.go
index c092419a5..0d56d26da 100644
--- a/internal/controller/rbac/provider/roles/roles.go
+++ b/internal/controller/rbac/provider/roles/roles.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 
+	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 )
 
@@ -154,6 +155,19 @@ func RenderClusterRoles(pr *v1.ProviderRevision, rs []Resource) []rbacv1.Cluster
 		Rules: withVerbs(rules, verbsEdit),
 	}
 
+	var viewRules []rbacv1.PolicyRule
+	if pkgmetav1.CapabilitiesContainFuzzyMatch(pr.GetCapabilities(), pkgmetav1.ProviderCapabilitySafeStart) {
+		viewRules = make([]rbacv1.PolicyRule, len(rules)+1)
+		copy(viewRules, rules)
+		viewRules[len(rules)] = rbacv1.PolicyRule{
+			APIGroups: []string{"apiextensions.k8s.io"},
+			Resources: []string{"customresourcedefinitions"},
+		}
+	} else {
+		// Default to the original rules.
+		viewRules = rules
+	}
+
 	view := &rbacv1.ClusterRole{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: namePrefix + pr.GetName() + nameSuffixView,
@@ -161,7 +175,7 @@ func RenderClusterRoles(pr *v1.ProviderRevision, rs []Resource) []rbacv1.Cluster
 				keyAggregateToView: valTrue,
 			},
 		},
-		Rules: withVerbs(rules, verbsView),
+		Rules: withVerbs(viewRules, verbsView),
 	}
 
 	// The 'system' RBAC role does not aggregate; it is intended to be bound
diff --git a/internal/converter/custom_to_managed.go b/internal/converter/custom_to_managed.go
new file mode 100644
index 000000000..a6b3109eb
--- /dev/null
+++ b/internal/converter/custom_to_managed.go
@@ -0,0 +1,96 @@
+/*
+Copyright 2025 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 converter holds resource converter helpers.
+package converter
+
+import (
+	"encoding/json"
+	"reflect"
+
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+// CustomResourceDefinition type metadata.
+//
+//nolint:gochecknoglobals // These should be globals.
+var (
+	customResourceDefinition     = reflect.TypeOf(extv1.CustomResourceDefinition{}).Name()
+	customResourceDefinitionKind = extv1.SchemeGroupVersion.WithKind(customResourceDefinition)
+)
+
+// CustomToManagedResourceDefinitions in place converts and returns any
+// CustomResourceDefinition runtime object into a ManagedResourceDefinition.
+func CustomToManagedResourceDefinitions(defaultActive bool, objects ...runtime.Object) ([]runtime.Object, error) {
+	var errs []error
+
+	extv1.Kind(customResourceDefinition)
+	for i, obj := range objects {
+		if obj.GetObjectKind().GroupVersionKind() == customResourceDefinitionKind {
+			// Don't mutate the original object just in-case it is used elsewhere.
+			obj := obj.DeepCopyObject()
+			// The object has to be either an unstructured.Unstructured object or a CustomResourceDefinition
+			switch o := obj.(type) {
+			// to covert, all we need to worry about is the metadata and spec.state.
+			case *unstructured.Unstructured:
+				mrdObject, err := convertCRDToMRD(defaultActive, o.Object)
+				if err != nil {
+					errs = append(errs, err)
+					continue
+				}
+				objects[i] = mrdObject
+			default:
+				b, err := json.Marshal(o)
+				if err != nil {
+					errs = append(errs, err)
+					continue
+				}
+				u := &unstructured.Unstructured{}
+				if err := json.Unmarshal(b, u); err != nil {
+					errs = append(errs, err)
+					continue
+				}
+				mrdObject, err := convertCRDToMRD(defaultActive, u.Object)
+				if err != nil {
+					errs = append(errs, err)
+					continue
+				}
+				objects[i] = mrdObject
+			}
+		}
+	}
+	return objects, errors.Join(errs...)
+}
+
+func convertCRDToMRD(defaultActive bool, in map[string]any) (*v2alpha1.ManagedResourceDefinition, error) {
+	in["apiVersion"] = v2alpha1.SchemeGroupVersion.String()
+	in["kind"] = v2alpha1.ManagedResourceDefinitionKind
+
+	var mrd v2alpha1.ManagedResourceDefinition
+
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(in, &mrd); err != nil {
+		return nil, errors.Wrap(err, "failed converting CRD to MRD")
+	}
+	if defaultActive {
+		mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+	}
+	return &mrd, nil
+}
diff --git a/internal/engine/engine_metrics.go b/internal/engine/engine_metrics.go
index 75a68d87d..28e8faa5c 100644
--- a/internal/engine/engine_metrics.go
+++ b/internal/engine/engine_metrics.go
@@ -47,25 +47,25 @@ type PrometheusMetrics struct {
 func NewPrometheusMetrics() *PrometheusMetrics {
 	return &PrometheusMetrics{
 		controllersStarted: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "engine",
 			Name:      "controllers_started_total",
-			Help:      "Total number of XR controllers started.",
+			Help:      "Total number of controllers started.",
 		}, []string{"controller"}),
 
 		controllersStopped: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "engine",
 			Name:      "controllers_stopped_total",
-			Help:      "Total number of XR controllers stopped.",
+			Help:      "Total number of controllers stopped.",
 		}, []string{"controller"}),
 
 		watchesStarted: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "engine",
 			Name:      "watches_started_total",
 			Help:      "Total number of watches started.",
 		}, []string{"controller", "type"}),
 
 		watchesStopped: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "engine",
 			Name:      "watches_stopped_total",
 			Help:      "Total number of watches stopped.",
 		}, []string{"controller", "type"}),
diff --git a/internal/features/features.go b/internal/features/features.go
index 017ea1999..7e27dd61f 100644
--- a/internal/features/features.go
+++ b/internal/features/features.go
@@ -65,4 +65,9 @@ const (
 	// compositions, i.e. watching composed resources and reconciling
 	// compositions immediately when any composed resource is updated.
 	EnableBetaRealtimeCompositions feature.Flag = "EnableBetaRealtimeCompositions"
+
+	// EnableBetaCustomToManagedResourceConversion enables beta support for
+	// custom resource definition to managed resource definition conversion.
+	// Conversion happens at provider install time.
+	EnableBetaCustomToManagedResourceConversion feature.Flag = "EnableBetaCustomToManagedResourceConversion"
 )
diff --git a/internal/initializer/initializer.go b/internal/initializer/initializer.go
index dbea4c611..bd4410de9 100644
--- a/internal/initializer/initializer.go
+++ b/internal/initializer/initializer.go
@@ -42,6 +42,9 @@ type StepFunc func(ctx context.Context, kube client.Client) error
 
 // Run calls the step function.
 func (f StepFunc) Run(ctx context.Context, kube client.Client) error {
+	if f == nil {
+		return nil
+	}
 	return f(ctx, kube)
 }
 
diff --git a/internal/initializer/managed_resource_activation_policy.go b/internal/initializer/managed_resource_activation_policy.go
new file mode 100644
index 000000000..b157a4a9a
--- /dev/null
+++ b/internal/initializer/managed_resource_activation_policy.go
@@ -0,0 +1,50 @@
+/*
+Copyright 2025 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 initializer
+
+import (
+	"context"
+
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+// DefaultManagedResourceActivationPolicy creates a "default" ManagedResourceActivationPolicy
+// object. It is a no-op if the object already exists.
+func DefaultManagedResourceActivationPolicy(activations ...v2alpha1.ActivationPolicy) StepFunc {
+	if len(activations) == 0 {
+		return nil
+	}
+	return func(ctx context.Context, kube client.Client) error {
+		rc := &v2alpha1.ManagedResourceActivationPolicy{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "default",
+			},
+			Spec: v2alpha1.ManagedResourceActivationPolicySpec{
+				Activations: activations,
+			},
+		}
+
+		return errors.Wrap(resource.Ignore(kerrors.IsAlreadyExists, kube.Create(ctx, rc)), "cannot create ManagedResourceActivationPolicy \"default\"")
+	}
+}
diff --git a/internal/names/generate.go b/internal/names/generate.go
index 4ade76957..eba1652a5 100644
--- a/internal/names/generate.go
+++ b/internal/names/generate.go
@@ -19,14 +19,19 @@ package names
 
 import (
 	"context"
+	"fmt"
+	"strings"
 
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apiserver/pkg/storage/names"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
+
+	"github.com/crossplane/crossplane/internal/xcrd"
 )
 
 const (
@@ -62,6 +67,18 @@ func NewNameGenerator(c client.Client) NameGenerator {
 	return &nameGenerator{reader: c, namer: names.SimpleNameGenerator}
 }
 
+func (r *nameGenerator) isAvailable(ctx context.Context, cd resource.Object, name string) (bool, error) {
+	obj := composite.Unstructured{}
+	obj.SetGroupVersionKind(cd.GetObjectKind().GroupVersionKind())
+
+	err := r.reader.Get(ctx, client.ObjectKey{Name: name, Namespace: cd.GetNamespace()}, &obj)
+	if kerrors.IsNotFound(err) {
+		return true, nil
+	}
+
+	return false, err
+}
+
 // GenerateName generates a name using the same algorithm as the API server, and
 // verifies temporary name availability. It does not submit the resource
 // to the API server and hence it does not fall over validation errors.
@@ -71,6 +88,23 @@ func (r *nameGenerator) GenerateName(ctx context.Context, cd resource.Object) er
 		return nil
 	}
 
+	// If we find the right information on the resource, try once.
+	cName := xcrd.GetCompositionResourceName(cd)
+	if cName != "" {
+		owner := metav1.GetControllerOf(cd)
+		if owner != nil && owner.UID != "" {
+			// We are going to roll the dice and hope no other XR child has a
+			// parent with the same uid ending in an effort to shorten the
+			// child name before the ChildName method has to trunc/add a hash.
+			uidParts := strings.Split(string(owner.UID), "-")
+			uidPart := uidParts[len(uidParts)-1]
+			name := ChildName(fmt.Sprintf("%s%s", cd.GetGenerateName(), uidPart), fmt.Sprintf("-%s", cName))
+			cd.SetName(name)
+			return nil
+		}
+	}
+	// Fallback to a random name.
+
 	// We guess a random name and verify that it is available. Names can become
 	// unavailable shortly after. We accept that very little risk of a name collision though:
 	// 1. with 8 million names, a collision against 10k names is 0.01%. We retry
@@ -86,19 +120,13 @@ func (r *nameGenerator) GenerateName(ctx context.Context, cd resource.Object) er
 	maxTries := 10
 	for range maxTries {
 		name := r.namer.GenerateName(cd.GetGenerateName())
-		obj := composite.Unstructured{}
-		obj.SetGroupVersionKind(cd.GetObjectKind().GroupVersionKind())
-
-		err := r.reader.Get(ctx, client.ObjectKey{Name: name}, &obj)
-		if kerrors.IsNotFound(err) {
+		if available, err := r.isAvailable(ctx, cd, name); err != nil {
+			return err
+		} else if available {
 			// The name is available.
 			cd.SetName(name)
 			return nil
 		}
-
-		if err != nil {
-			return err
-		}
 	}
 
 	return errors.New(errGenerateName)
diff --git a/internal/names/names.go b/internal/names/names.go
new file mode 100644
index 000000000..6ab2a0ae3
--- /dev/null
+++ b/internal/names/names.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2025 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.
+*/
+
+// Forked from https://github.com/knative/pkg/blob/ee1db869c7ef25eb4ac5c9ba0ab73fdc3f1b9dfa/kmeta/names.go
+
+/*
+copyright 2019 the knative authors
+
+licensed under the apache license, version 2.0 (the "license");
+you may not use this file except in compliance with the license.
+you may obtain a copy of the license at
+
+    http://www.apache.org/licenses/license-2.0
+
+unless required by applicable law or agreed to in writing, software
+distributed under the license is distributed on an "as is" basis,
+without warranties or conditions of any kind, either express or implied.
+see the license for the specific language governing permissions and
+limitations under the license.
+*/
+
+package names
+
+import (
+	"crypto/md5" //nolint:gosec // No strong cryptography needed.
+	"encoding/hex"
+	"fmt"
+	"regexp"
+)
+
+// The longest name supported by the K8s is 63.
+// These constants.
+const (
+	longest = 63
+	md5Len  = 32
+	head    = longest - md5Len // How much to truncate to fit the hash.
+)
+
+var isAlphanumeric = regexp.MustCompile(`^[a-zA-Z0-9]*$`)
+
+// ChildName generates a name for the resource based upon the parent resource and suffix.
+// If the concatenated name is longer than K8s permits the name is hashed and truncated to permit
+// construction of the resource, but still keeps it unique.
+// If the suffix itself is longer than 31 characters, then the whole string will be hashed
+// and `parent|hash|suffix` will be returned, where parent and suffix will be trimmed to
+// fit (prefix of parent at most of length 31, and prefix of suffix at most length 30).
+func ChildName(parent, suffix string) string {
+	n := parent
+	if len(parent) > (longest - len(suffix)) {
+		// If the suffix is longer than the longest allowed suffix, then
+		// we hash the whole combined string and use that as the suffix.
+		if head-len(suffix) <= 0 {
+			//nolint:gosec // No strong cryptography needed.
+			h := md5.Sum([]byte(parent + suffix))
+			// 1. trim parent, if needed
+			if head < len(parent) {
+				parent = parent[:head]
+			}
+			// Format the return string, if it's shorter than longest: pad with
+			// beginning of the suffix. This happens, for example, when parent is
+			// short, but the suffix is very long.
+			ret := parent + hex.EncodeToString(h[:])
+			if d := longest - len(ret); d > 0 {
+				ret += suffix[:d]
+			}
+			return makeValidName(ret)
+		}
+		//nolint:gosec // No strong cryptography needed.
+		n = fmt.Sprintf("%s%x", parent[:head-len(suffix)], md5.Sum([]byte(parent)))
+	}
+	return n + suffix
+}
+
+// If due to trimming above we're terminating the string with a non-alphanumeric
+// character, remove it.
+func makeValidName(n string) string {
+	for i := len(n) - 1; !isAlphanumeric.MatchString(string(n[i])); i-- {
+		n = n[:len(n)-1]
+	}
+	return n
+}
diff --git a/internal/ops/lifecycle/operations.go b/internal/ops/lifecycle/operations.go
new file mode 100644
index 000000000..0db313eb6
--- /dev/null
+++ b/internal/ops/lifecycle/operations.go
@@ -0,0 +1,121 @@
+/*
+Copyright 2025 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 lifecycle provides utilities for managing the lifecycle of Operations.
+package lifecycle
+
+import (
+	"slices"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+)
+
+// LatestCreateTime returns the latest creation timestamp of a set of
+// Operations.
+func LatestCreateTime(ops ...v1alpha1.Operation) time.Time {
+	latest := time.Time{}
+
+	for _, op := range ops {
+		if t := op.GetCreationTimestamp(); t.After(latest) {
+			latest = t.Time
+		}
+	}
+
+	return latest
+}
+
+// LatestSucceededTransitionTime returns the latest transition timestamp for the Succeeded
+// condition for a set of Operations.
+func LatestSucceededTransitionTime(ops ...v1alpha1.Operation) time.Time {
+	latest := time.Time{}
+
+	for _, op := range ops {
+		if t := op.GetCondition(v1alpha1.TypeSucceeded).LastTransitionTime; t.After(latest) {
+			latest = t.Time
+		}
+	}
+
+	return latest
+}
+
+// WithReason filters the supplied operations to only the ones that have the
+// supplied Succeeded condition reason.
+func WithReason(r xpv1.ConditionReason, ops ...v1alpha1.Operation) []v1alpha1.Operation {
+	out := make([]v1alpha1.Operation, 0)
+	for _, op := range ops {
+		if op.GetCondition(v1alpha1.TypeSucceeded).Reason == r {
+			out = append(out, op)
+		}
+	}
+	return out
+}
+
+// MarkGarbage accepts a number of succeeded and failed Operations to keep. It
+// returns the slice of Operations that should be deleted. It keeps the Operations
+// with the most recent creation timestamps. If a limit is zero, no Operations
+// of that type will be kept.
+func MarkGarbage(keepSucceeded, keepFailed int32, ops ...v1alpha1.Operation) []v1alpha1.Operation {
+	del := make([]v1alpha1.Operation, 0)
+
+	// Sort latest first.
+	slices.SortFunc(ops, func(a, b v1alpha1.Operation) int {
+		switch {
+		case a.GetCreationTimestamp().Time.Before(b.GetCreationTimestamp().Time):
+			return 1
+		case a.GetCreationTimestamp().Time.Equal(b.GetCreationTimestamp().Time):
+			return 0
+		default:
+			return -1
+		}
+	})
+
+	var keptSucceeded, keptFailed int32 = 0, 0
+	for _, op := range ops {
+		s := op.GetCondition(v1alpha1.TypeSucceeded).Status
+		switch s {
+		case corev1.ConditionTrue:
+			if keptSucceeded < keepSucceeded {
+				keptSucceeded++
+				continue
+			}
+			del = append(del, op)
+		case corev1.ConditionFalse:
+			if keptFailed < keepFailed {
+				keptFailed++
+				continue
+			}
+			del = append(del, op)
+		case corev1.ConditionUnknown:
+			// Keep it - operation is still running.
+		}
+	}
+
+	return del
+}
+
+// RunningOperationRefs returns RunningOperationRefs to the supplied names.
+func RunningOperationRefs(running []string) []v1alpha1.RunningOperationRef {
+	out := make([]v1alpha1.RunningOperationRef, len(running))
+	for i := range running {
+		out[i] = v1alpha1.RunningOperationRef{Name: running[i]}
+	}
+	return out
+}
diff --git a/internal/controller/apiextensions/composite/composite.go b/internal/xcrd/composite.go
similarity index 77%
rename from internal/controller/apiextensions/composite/composite.go
rename to internal/xcrd/composite.go
index 627917c8b..5812044e3 100644
--- a/internal/controller/apiextensions/composite/composite.go
+++ b/internal/xcrd/composite.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package composite
+package xcrd
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -24,17 +24,18 @@ import (
 
 // Annotation keys.
 const (
+	// AnnotationKeyCompositionResourceName is the name of the composite resource as described from a composition.
 	AnnotationKeyCompositionResourceName = "crossplane.io/composition-resource-name"
 )
 
 // SetCompositionResourceName sets the name of the composition template used to
 // reconcile a composed resource as an annotation.
-func SetCompositionResourceName(o metav1.Object, n ResourceName) {
-	meta.AddAnnotations(o, map[string]string{AnnotationKeyCompositionResourceName: string(n)})
+func SetCompositionResourceName(o metav1.Object, n string) {
+	meta.AddAnnotations(o, map[string]string{AnnotationKeyCompositionResourceName: n})
 }
 
 // GetCompositionResourceName gets the name of the composition template used to
 // reconcile a composed resource from its annotations.
-func GetCompositionResourceName(o metav1.Object) ResourceName {
-	return ResourceName(o.GetAnnotations()[AnnotationKeyCompositionResourceName])
+func GetCompositionResourceName(o metav1.Object) string {
+	return o.GetAnnotations()[AnnotationKeyCompositionResourceName]
 }
diff --git a/internal/xcrd/schemas.go b/internal/xcrd/schemas.go
index 78f46aed0..62c60cafe 100644
--- a/internal/xcrd/schemas.go
+++ b/internal/xcrd/schemas.go
@@ -320,19 +320,8 @@ func CompositeResourceStatusProps(s v1.CompositeResourceScope) map[string]extv1.
 
 	switch s {
 	case v1.CompositeResourceScopeNamespaced, v1.CompositeResourceScopeCluster:
-		// Modern XRs use status.crossplane, and don't support claims.
-		props["crossplane"] = extv1.JSONSchemaProps{
-			Type:        "object",
-			Description: "Indicates how Crossplane is reconciling this composite resource",
-			Properties: map[string]extv1.JSONSchemaProps{
-				"connectionDetails": {
-					Type: "object",
-					Properties: map[string]extv1.JSONSchemaProps{
-						"lastPublishedTime": {Type: "string", Format: "date-time"},
-					},
-				},
-			},
-		}
+		// Modern XRs don't have connection details or support claims, so
+		// there's nothing else to put in the status for them
 	case v1.CompositeResourceScopeLegacyCluster:
 		// Legacy XRs don't use status.crossplane, and support claims.
 		props["connectionDetails"] = extv1.JSONSchemaProps{
diff --git a/internal/xfn/cached/cached_runner_metrics.go b/internal/xfn/cached/cached_runner_metrics.go
index 7acffad52..7ebe44b2f 100644
--- a/internal/xfn/cached/cached_runner_metrics.go
+++ b/internal/xfn/cached/cached_runner_metrics.go
@@ -76,56 +76,56 @@ type PrometheusMetrics struct {
 func NewPrometheusMetrics() *PrometheusMetrics {
 	return &PrometheusMetrics{
 		hits: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_hits_total",
 			Help:      "Total number of RunFunctionResponse cache hits.",
 		}, []string{"function_name"}),
 
 		misses: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_misses_total",
 			Help:      "Total number of RunFunctionResponse cache misses.",
 		}, []string{"function_name"}),
 
 		errors: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_errors_total",
 			Help:      "Total number of RunFunctionResponse cache errors.",
 		}, []string{"function_name"}),
 
 		writes: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_writes_total",
 			Help:      "Total number of RunFunctionResponses cache writes.",
 		}, []string{"function_name"}),
 
 		deletes: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_deletes_total",
 			Help:      "Total number of RunFunctionResponses cache deletes.",
 		}, []string{"function_name"}),
 
 		bytesWritten: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_bytes_written_total",
 			Help:      "Total number of RunFunctionResponse bytes written.",
 		}, []string{"function_name"}),
 
 		bytesDeleted: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_bytes_deleted_total",
 			Help:      "Total number of RunFunctionResponse bytes deleted.",
 		}, []string{"function_name"}),
 
 		readDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_read_seconds",
 			Help:      "Histogram of RunFunctionResponse cache read time (seconds).",
 			Buckets:   prometheus.DefBuckets,
 		}, []string{"function_name"}),
 
 		writeDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_cache_write_seconds",
 			Help:      "Histogram of RunFunctionResponse cache write time (seconds).",
 			Buckets:   prometheus.DefBuckets,
diff --git a/internal/xfn/function_runner_metrics.go b/internal/xfn/function_runner_metrics.go
index c16ca9a35..884697b46 100644
--- a/internal/xfn/function_runner_metrics.go
+++ b/internal/xfn/function_runner_metrics.go
@@ -26,7 +26,7 @@ import (
 	fnv1 "github.com/crossplane/crossplane/proto/fn/v1"
 )
 
-// PrometheusMetrics are requests, errors, and duration (RED) metrics for composition
+// PrometheusMetrics are requests, errors, and duration (RED) metrics for
 // function runs.
 type PrometheusMetrics struct {
 	requests  *prometheus.CounterVec
@@ -34,23 +34,23 @@ type PrometheusMetrics struct {
 	duration  *prometheus.HistogramVec
 }
 
-// NewPrometheusMetrics creates metrics for composition function runs.
+// NewPrometheusMetrics creates metrics for function runs.
 func NewPrometheusMetrics() *PrometheusMetrics {
 	return &PrometheusMetrics{
 		requests: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_request_total",
 			Help:      "Total number of RunFunctionRequests sent.",
 		}, []string{"function_name", "function_package", "grpc_target", "grpc_method"}),
 
 		responses: prometheus.NewCounterVec(prometheus.CounterOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_response_total",
 			Help:      "Total number of RunFunctionResponses received.",
 		}, []string{"function_name", "function_package", "grpc_target", "grpc_method", "grpc_code", "result_severity"}),
 
 		duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
-			Subsystem: "composition",
+			Subsystem: "function",
 			Name:      "run_function_seconds",
 			Help:      "Histogram of RunFunctionResponse latency (seconds).",
 			Buckets:   prometheus.DefBuckets,
diff --git a/internal/xfn/required_resources.go b/internal/xfn/required_resources.go
index 5e61c0d1a..7ca78459c 100644
--- a/internal/xfn/required_resources.go
+++ b/internal/xfn/required_resources.go
@@ -17,8 +17,8 @@ package xfn
 
 import (
 	"context"
-	"reflect"
 
+	"google.golang.org/protobuf/proto"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/types"
@@ -81,7 +81,7 @@ func (c *FetchingFunctionRunner) RunFunction(ctx context.Context, name string, r
 		}
 
 		newRequirements := rsp.GetRequirements()
-		if reflect.DeepEqual(newRequirements, requirements) {
+		if proto.Equal(newRequirements, requirements) {
 			// The requirements stabilized, the function is done.
 			return rsp, nil
 		}
diff --git a/internal/xpkg/lint.go b/internal/xpkg/lint.go
index 07f58e75a..fd0ddb4d2 100644
--- a/internal/xpkg/lint.go
+++ b/internal/xpkg/lint.go
@@ -27,7 +27,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/parser"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
-	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+	v2 "github.com/crossplane/crossplane/apis/apiextensions/v2"
 	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
 	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
 	"github.com/crossplane/crossplane/internal/version"
@@ -45,6 +45,8 @@ const (
 	errNotValidatingWebhookConfiguration = "object is not an ValidatingWebhookConfiguration"
 	errNotComposition                    = "object is not a Composition"
 	errNotOperation                      = "object is not an Operation"
+	errNotCronOperation                  = "object is not a CronOperation"
+	errNotWatchOperation                 = "object is not a WatchOperation"
 	errBadConstraints                    = "package version constraints are poorly formatted"
 	errFmtCrossplaneIncompatible         = "package is not compatible with Crossplane version (%s)"
 )
@@ -68,7 +70,7 @@ func NewConfigurationLinter() parser.Linter {
 	return parser.NewPackageLinter(
 		parser.PackageLinterFns(OneMeta),
 		parser.ObjectLinterFns(IsConfiguration, PackageValidSemver),
-		parser.ObjectLinterFns(parser.Or(IsXRD, IsComposition, IsOperation)))
+		parser.ObjectLinterFns(parser.Or(IsXRD, IsComposition, IsOperation, IsCronOperation, IsWatchOperation)))
 }
 
 // NewFunctionLinter is a convenience function for creating a package linter for
@@ -194,7 +196,7 @@ func IsValidatingWebhookConfiguration(o runtime.Object) error {
 // IsXRD checks that an object is a CompositeResourceDefinition.
 func IsXRD(o runtime.Object) error {
 	switch o.(type) {
-	case *v1.CompositeResourceDefinition, *v2alpha1.CompositeResourceDefinition:
+	case *v1.CompositeResourceDefinition, *v2.CompositeResourceDefinition:
 		return nil
 	default:
 		return errors.New(errNotXRD)
@@ -218,3 +220,21 @@ func IsOperation(o runtime.Object) error {
 
 	return nil
 }
+
+// IsCronOperation checks that an object is a CronOperation.
+func IsCronOperation(o runtime.Object) error {
+	if _, ok := o.(*v1alpha1.CronOperation); !ok {
+		return errors.New(errNotCronOperation)
+	}
+
+	return nil
+}
+
+// IsWatchOperation checks that an object is a WatchOperation.
+func IsWatchOperation(o runtime.Object) error {
+	if _, ok := o.(*v1alpha1.WatchOperation); !ok {
+		return errors.New(errNotWatchOperation)
+	}
+
+	return nil
+}
diff --git a/internal/xpkg/scheme.go b/internal/xpkg/scheme.go
index 8348e629a..359782e57 100644
--- a/internal/xpkg/scheme.go
+++ b/internal/xpkg/scheme.go
@@ -24,7 +24,9 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/conversion"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+	v2 "github.com/crossplane/crossplane/apis/apiextensions/v2"
 	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+	opsv1alpha1 "github.com/crossplane/crossplane/apis/ops/v1alpha1"
 	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
 	pkgmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1"
 	pkgmetav1beta1 "github.com/crossplane/crossplane/apis/pkg/meta/v1beta1"
@@ -61,6 +63,14 @@ func BuildObjectScheme() (*runtime.Scheme, error) {
 		return nil, err
 	}
 
+	if err := opsv1alpha1.AddToScheme(objScheme); err != nil {
+		return nil, err
+	}
+
+	if err := v2.AddToScheme(objScheme); err != nil {
+		return nil, err
+	}
+
 	if err := extv1beta1.AddToScheme(objScheme); err != nil {
 		return nil, err
 	}



Test patch:
diff --git a/apis/apiextensions/v2alpha1/mrd_policy_types_test.go b/apis/apiextensions/v2alpha1/mrd_policy_types_test.go
new file mode 100644
index 000000000..bef86d307
--- /dev/null
+++ b/apis/apiextensions/v2alpha1/mrd_policy_types_test.go
@@ -0,0 +1,740 @@
+/*
+Copyright 2025 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 v2alpha1
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestActivationPolicyMatch(t *testing.T) {
+	type args struct {
+		policy ActivationPolicy
+		name   string
+	}
+	type want struct {
+		match bool
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"ExactMatch": {
+			reason: "Should match exact name",
+			args: args{
+				policy: ActivationPolicy("bucket.aws.crossplane.io"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"ExactMatchNoMatch": {
+			reason: "Should not match different exact name",
+			args: args{
+				policy: ActivationPolicy("bucket.aws.crossplane.io"),
+				name:   "instance.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"WildcardPrefixMatch": {
+			reason: "Should match name with wildcard prefix",
+			args: args{
+				policy: ActivationPolicy("*.aws.crossplane.io"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"WildcardPrefixMatchMultiple": {
+			reason: "Should match multiple components with wildcard prefix",
+			args: args{
+				policy: ActivationPolicy("*.aws.crossplane.io"),
+				name:   "rds.instance.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"WildcardPrefixNoMatch": {
+			reason: "Should not match different domain with wildcard prefix",
+			args: args{
+				policy: ActivationPolicy("*.aws.crossplane.io"),
+				name:   "bucket.gcp.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"WildcardSuffixMatch": {
+			reason: "Should match name with wildcard suffix",
+			args: args{
+				policy: ActivationPolicy("bucket.*"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"WildcardSuffixNoMatch": {
+			reason: "Should not match different prefix with wildcard suffix",
+			args: args{
+				policy: ActivationPolicy("bucket.*"),
+				name:   "instance.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"WildcardBothEndsMatch": {
+			reason: "Should match name with wildcards at both ends",
+			args: args{
+				policy: ActivationPolicy("*aws*"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"WildcardBothEndsNoMatch": {
+			reason: "Should not match name without pattern with wildcards at both ends",
+			args: args{
+				policy: ActivationPolicy("*aws*"),
+				name:   "bucket.gcp.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"WildcardOnlyMatch": {
+			reason: "Should match any name with wildcard only",
+			args: args{
+				policy: ActivationPolicy("*"),
+				name:   "anything.goes.here",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"EmptyPolicyNoMatch": {
+			reason: "Should not match non-empty name with empty policy",
+			args: args{
+				policy: ActivationPolicy(""),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"EmptyPolicyEmptyNameMatch": {
+			reason: "Should match empty name with empty policy",
+			args: args{
+				policy: ActivationPolicy(""),
+				name:   "",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"EmptyNameNoMatch": {
+			reason: "Should not match empty name with non-empty policy",
+			args: args{
+				policy: ActivationPolicy("bucket.aws.crossplane.io"),
+				name:   "",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"ComplexWildcardMatch": {
+			reason: "Should match complex wildcard pattern",
+			args: args{
+				policy: ActivationPolicy("*bucket*.aws.*"),
+				name:   "s3bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"ComplexWildcardNoMatch": {
+			reason: "Should not match name that doesn't fit complex wildcard pattern",
+			args: args{
+				policy: ActivationPolicy("*bucket*.aws.*"),
+				name:   "instance.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"QuestionMarkWildcardMatch": {
+			reason: "Should match single character with question mark wildcard",
+			args: args{
+				policy: ActivationPolicy("bucket?.aws.crossplane.io"),
+				name:   "bucket1.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"QuestionMarkWildcardNoMatch": {
+			reason: "Should not match multiple characters with question mark wildcard",
+			args: args{
+				policy: ActivationPolicy("bucket?.aws.crossplane.io"),
+				name:   "bucket123.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"CharacterClassMatch": {
+			reason: "Should match character class pattern",
+			args: args{
+				policy: ActivationPolicy("bucket[0-9].aws.crossplane.io"),
+				name:   "bucket5.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"CharacterClassNoMatch": {
+			reason: "Should not match character outside class pattern",
+			args: args{
+				policy: ActivationPolicy("bucket[0-9].aws.crossplane.io"),
+				name:   "bucketa.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"EscapedWildcardMatch": {
+			reason: "Should match literal asterisk when escaped",
+			args: args{
+				policy: ActivationPolicy("bucket\\*.aws.crossplane.io"),
+				name:   "bucket*.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"EscapedWildcardNoMatch": {
+			reason: "Should not match expanded form when asterisk is escaped",
+			args: args{
+				policy: ActivationPolicy("bucket\\*.aws.crossplane.io"),
+				name:   "bucket123.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"CaseSensitiveMatch": {
+			reason: "Should be case sensitive for exact match",
+			args: args{
+				policy: ActivationPolicy("Bucket.aws.crossplane.io"),
+				name:   "Bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"CaseSensitiveNoMatch": {
+			reason: "Should not match different case",
+			args: args{
+				policy: ActivationPolicy("Bucket.aws.crossplane.io"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"WildcardCaseSensitiveMatch": {
+			reason: "Should be case sensitive with wildcard",
+			args: args{
+				policy: ActivationPolicy("*.AWS.crossplane.io"),
+				name:   "bucket.AWS.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+		"WildcardCaseSensitiveNoMatch": {
+			reason: "Should not match different case with wildcard",
+			args: args{
+				policy: ActivationPolicy("*.AWS.crossplane.io"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: false,
+			},
+		},
+		"DotInWildcardMatch": {
+			reason: "Should match dot characters in wildcard",
+			args: args{
+				policy: ActivationPolicy("*.crossplane.io"),
+				name:   "bucket.aws.crossplane.io",
+			},
+			want: want{
+				match: true,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := tc.args.policy.Match(tc.args.name)
+
+			if diff := cmp.Diff(tc.want.match, got); diff != "" {
+				t.Errorf("\n%s\nMatch(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestManagedResourceActivationPolicyStatusAppendActivated(t *testing.T) {
+	type args struct {
+		status *ManagedResourceActivationPolicyStatus
+		name   string
+	}
+	type want struct {
+		activated []string
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"NilActivatedList": {
+			reason: "Should create new activated list when nil",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: nil,
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io"},
+			},
+		},
+		"EmptyActivatedList": {
+			reason: "Should append to empty activated list",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io"},
+			},
+		},
+		"SingleItemList": {
+			reason: "Should append to single item list and maintain sort order",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"instance.aws.crossplane.io"},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io", "instance.aws.crossplane.io"},
+			},
+		},
+		"MultipleItemsList": {
+			reason: "Should append to multiple items list and maintain sort order",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"database.aws.crossplane.io", "instance.aws.crossplane.io"},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io", "database.aws.crossplane.io", "instance.aws.crossplane.io"},
+			},
+		},
+		"AppendAtEnd": {
+			reason: "Should append item that belongs at end of sorted list",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"bucket.aws.crossplane.io", "database.aws.crossplane.io"},
+				},
+				name: "instance.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io", "database.aws.crossplane.io", "instance.aws.crossplane.io"},
+			},
+		},
+		"AppendAtBeginning": {
+			reason: "Should append item that belongs at beginning of sorted list",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"database.aws.crossplane.io", "instance.aws.crossplane.io"},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io", "database.aws.crossplane.io", "instance.aws.crossplane.io"},
+			},
+		},
+		"AppendInMiddle": {
+			reason: "Should append item that belongs in middle of sorted list",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"bucket.aws.crossplane.io", "instance.aws.crossplane.io"},
+				},
+				name: "database.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io", "database.aws.crossplane.io", "instance.aws.crossplane.io"},
+			},
+		},
+		"DuplicateItem": {
+			reason: "Should handle duplicate items correctly with sorting",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"bucket.aws.crossplane.io", "instance.aws.crossplane.io"},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{"bucket.aws.crossplane.io", "bucket.aws.crossplane.io", "instance.aws.crossplane.io"},
+			},
+		},
+		"LargeList": {
+			reason: "Should handle large activated list efficiently",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{
+						"resource1.aws.crossplane.io",
+						"resource2.aws.crossplane.io",
+						"resource3.aws.crossplane.io",
+						"resource4.aws.crossplane.io",
+						"resource5.aws.crossplane.io",
+						"resource6.aws.crossplane.io",
+						"resource7.aws.crossplane.io",
+						"resource8.aws.crossplane.io",
+						"resource9.aws.crossplane.io",
+					},
+				},
+				name: "resource10.aws.crossplane.io",
+			},
+			want: want{
+				activated: []string{
+					"resource1.aws.crossplane.io",
+					"resource10.aws.crossplane.io",
+					"resource2.aws.crossplane.io",
+					"resource3.aws.crossplane.io",
+					"resource4.aws.crossplane.io",
+					"resource5.aws.crossplane.io",
+					"resource6.aws.crossplane.io",
+					"resource7.aws.crossplane.io",
+					"resource8.aws.crossplane.io",
+					"resource9.aws.crossplane.io",
+				},
+			},
+		},
+		"EmptyName": {
+			reason: "Should handle empty name correctly",
+			args: args{
+				status: &ManagedResourceActivationPolicyStatus{
+					Activated: []string{"bucket.aws.crossplane.io"},
+				},
+				name: "",
+			},
+			want: want{
+				activated: []string{"", "bucket.aws.crossplane.io"},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			tc.args.status.AppendActivated(tc.args.name)
+
+			if diff := cmp.Diff(tc.want.activated, tc.args.status.Activated); diff != "" {
+				t.Errorf("\n%s\nAppendActivated(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestManagedResourceActivationPolicyActivates(t *testing.T) {
+	type args struct {
+		policy *ManagedResourceActivationPolicy
+		name   string
+	}
+	type want struct {
+		activate bool
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"EmptyActivationList": {
+			reason: "Should not activate any name when activation list is empty",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{},
+					},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activate: false,
+			},
+		},
+		"NilActivationList": {
+			reason: "Should not activate any name when activation list is nil",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: nil,
+					},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activate: false,
+			},
+		},
+		"SingleActivationMatch": {
+			reason: "Should activate name that matches single activation policy",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+						},
+					},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+		"SingleActivationNoMatch": {
+			reason: "Should not activate name that doesn't match single activation policy",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+						},
+					},
+				},
+				name: "instance.aws.crossplane.io",
+			},
+			want: want{
+				activate: false,
+			},
+		},
+		"MultipleActivationsFirstMatch": {
+			reason: "Should activate name that matches first of multiple activation policies",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+							"instance.aws.crossplane.io",
+							"*.gcp.crossplane.io",
+						},
+					},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+		"MultipleActivationsMiddleMatch": {
+			reason: "Should activate name that matches middle of multiple activation policies",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+							"instance.aws.crossplane.io",
+							"*.gcp.crossplane.io",
+						},
+					},
+				},
+				name: "instance.aws.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+		"MultipleActivationsLastMatch": {
+			reason: "Should activate name that matches last of multiple activation policies",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+							"instance.aws.crossplane.io",
+							"*.gcp.crossplane.io",
+						},
+					},
+				},
+				name: "storage.gcp.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+		"MultipleActivationsNoMatch": {
+			reason: "Should not activate name that doesn't match any of multiple activation policies",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+							"instance.aws.crossplane.io",
+							"*.gcp.crossplane.io",
+						},
+					},
+				},
+				name: "database.azure.crossplane.io",
+			},
+			want: want{
+				activate: false,
+			},
+		},
+		"MultipleActivationsWildcardMatch": {
+			reason: "Should activate name that matches wildcard in multiple activation policies",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"specific.aws.crossplane.io",
+							"*.aws.crossplane.io",
+							"exact.gcp.crossplane.io",
+						},
+					},
+				},
+				name: "anything.aws.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+		"LargeActivationList": {
+			reason: "Should handle large activation list efficiently",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"resource1.aws.crossplane.io",
+							"resource2.aws.crossplane.io",
+							"resource3.aws.crossplane.io",
+							"resource4.aws.crossplane.io",
+							"resource5.aws.crossplane.io",
+							"resource6.aws.crossplane.io",
+							"resource7.aws.crossplane.io",
+							"resource8.aws.crossplane.io",
+							"resource9.aws.crossplane.io",
+							"resource10.aws.crossplane.io",
+						},
+					},
+				},
+				name: "resource5.aws.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+		"LargeActivationListNoMatch": {
+			reason: "Should not activate when name doesn't match any in large activation list",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"resource1.aws.crossplane.io",
+							"resource2.aws.crossplane.io",
+							"resource3.aws.crossplane.io",
+							"resource4.aws.crossplane.io",
+							"resource5.aws.crossplane.io",
+							"resource6.aws.crossplane.io",
+							"resource7.aws.crossplane.io",
+							"resource8.aws.crossplane.io",
+							"resource9.aws.crossplane.io",
+							"resource10.aws.crossplane.io",
+						},
+					},
+				},
+				name: "resource11.aws.crossplane.io",
+			},
+			want: want{
+				activate: false,
+			},
+		},
+		"EmptyNameWithActivations": {
+			reason: "Should not activate empty name even with activations present",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+							"*.aws.crossplane.io",
+						},
+					},
+				},
+				name: "",
+			},
+			want: want{
+				activate: false,
+			},
+		},
+		"EmptyPolicyInList": {
+			reason: "Should handle empty policy in activation list",
+			args: args{
+				policy: &ManagedResourceActivationPolicy{
+					Spec: ManagedResourceActivationPolicySpec{
+						Activations: []ActivationPolicy{
+							"bucket.aws.crossplane.io",
+							"", // empty policy
+							"*.gcp.crossplane.io",
+						},
+					},
+				},
+				name: "bucket.aws.crossplane.io",
+			},
+			want: want{
+				activate: true,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := tc.args.policy.Activates(tc.args.name)
+
+			if diff := cmp.Diff(tc.want.activate, got); diff != "" {
+				t.Errorf("\n%s\nActivates(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/apis/pkg/meta/v1/capabilities_test.go b/apis/pkg/meta/v1/capabilities_test.go
new file mode 100644
index 000000000..b41b129f2
--- /dev/null
+++ b/apis/pkg/meta/v1/capabilities_test.go
@@ -0,0 +1,180 @@
+/*
+Copyright 2025 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 v1
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestCapabilitiesContainFuzzy(t *testing.T) {
+	type args struct {
+		capabilities []string
+		key          string
+	}
+	type want struct {
+		result bool
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"ExactMatch": {
+			reason: "Should return true for exact match",
+			args: args{
+				capabilities: []string{"composition", "operation"},
+				key:          "composition",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"CaseInsensitiveMatch": {
+			reason: "Should return true for case insensitive match",
+			args: args{
+				capabilities: []string{"Composition", "OPERATION"},
+				key:          "composition",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"HyphenatedMatch": {
+			reason: "Should return true when matching hyphenated capability",
+			args: args{
+				capabilities: []string{"safe-start", "composition"},
+				key:          "safe-start",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"FuzzyMatchWithSpecialChars": {
+			reason: "Should return true when key has special characters that are stripped",
+			args: args{
+				capabilities: []string{"safestart", "composition"},
+				key:          "safe-start",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"FuzzyMatchDifferentSeparators": {
+			reason: "Should return true when capability and key use different separators",
+			args: args{
+				capabilities: []string{"safe_start", "composition"},
+				key:          "safe-start",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"NoMatch": {
+			reason: "Should return false when no capability matches",
+			args: args{
+				capabilities: []string{"composition", "operation"},
+				key:          "nonexistent",
+			},
+			want: want{
+				result: false,
+			},
+		},
+		"EmptyCapabilities": {
+			reason: "Should return false when capabilities list is empty",
+			args: args{
+				capabilities: []string{},
+				key:          "composition",
+			},
+			want: want{
+				result: false,
+			},
+		},
+		"EmptyKey": {
+			reason: "Should return true when key is empty and empty capability exists",
+			args: args{
+				capabilities: []string{"", "composition"},
+				key:          "",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"EmptyKeyNoMatch": {
+			reason: "Should return false when key is empty and no empty capability exists",
+			args: args{
+				capabilities: []string{"composition", "operation"},
+				key:          "",
+			},
+			want: want{
+				result: false,
+			},
+		},
+		"SpecialCharactersOnly": {
+			reason: "Should return true when both capability and key reduce to empty after stripping special chars",
+			args: args{
+				capabilities: []string{"---", "composition"},
+				key:          "___",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"NumbersInCapability": {
+			reason: "Should return true when capability contains numbers",
+			args: args{
+				capabilities: []string{"v1-composition", "operation"},
+				key:          "v1composition",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"UnicodeCharacters": {
+			reason: "Should return true when matching with unicode characters stripped",
+			args: args{
+				capabilities: []string{"safe→start", "composition"},
+				key:          "safestart",
+			},
+			want: want{
+				result: true,
+			},
+		},
+		"MixedCaseAndSpecialChars": {
+			reason: "Should return true when matching mixed case with special characters",
+			args: args{
+				capabilities: []string{"Safe-Start", "Composition"},
+				key:          "SAFE_START",
+			},
+			want: want{
+				result: true,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := CapabilitiesContainFuzzyMatch(tc.args.capabilities, tc.args.key)
+
+			if diff := cmp.Diff(tc.want.result, got); diff != "" {
+				t.Errorf("\n%s\nCapabilitiesContainFuzzy(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/cmd/crank/beta/top/top_test.go b/cmd/crank/beta/top/top_test.go
index 0be9f68eb..246c0570b 100644
--- a/cmd/crank/beta/top/top_test.go
+++ b/cmd/crank/beta/top/top_test.go
@@ -11,6 +11,8 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 )
 
 func TestGetCrossplanePods(t *testing.T) {
@@ -40,7 +42,7 @@ func TestGetCrossplanePods(t *testing.T) {
 						Name:      "function-12345abcd-xyzwv",
 						Namespace: "crossplane-system",
 						Labels: map[string]string{
-							"pkg.crossplane.io/function": "function-go-templating",
+							v1.LabelFunction: "function-go-templating",
 						},
 					},
 				},
@@ -88,7 +90,7 @@ func TestGetCrossplanePods(t *testing.T) {
 						Name:      "function-go-templating-213wer",
 						Namespace: "crossplane-system",
 						Labels: map[string]string{
-							"pkg.crossplane.io/function": "function-go-templating",
+							v1.LabelFunction: "function-go-templating",
 						},
 					},
 				},
@@ -97,7 +99,7 @@ func TestGetCrossplanePods(t *testing.T) {
 						Name:      "provider-azure-storage",
 						Namespace: "crossplane-system",
 						Labels: map[string]string{
-							"pkg.crossplane.io/provider": "provider-azure-storage",
+							v1.LabelProvider: "provider-azure-storage",
 						},
 					},
 				},
diff --git a/cmd/crank/beta/trace/internal/printer/printer_test.go b/cmd/crank/beta/trace/internal/printer/printer_test.go
index f072aac53..106de3923 100644
--- a/cmd/crank/beta/trace/internal/printer/printer_test.go
+++ b/cmd/crank/beta/trace/internal/printer/printer_test.go
@@ -26,7 +26,7 @@ import (
 
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 	"github.com/crossplane/crossplane/cmd/crank/beta/trace/internal/resource"
-	"github.com/crossplane/crossplane/internal/controller/apiextensions/composite"
+	"github.com/crossplane/crossplane/internal/xcrd"
 )
 
 // DummyManifestOpt can be passed to customize a dummy manifest.
@@ -75,7 +75,7 @@ func WithConditions(conds ...xpv1.Condition) DummyManifestOpt {
 
 func WithCompositionResourceName(n string) DummyManifestOpt {
 	return func(m *unstructured.Unstructured) {
-		meta.AddAnnotations(m, map[string]string{composite.AnnotationKeyCompositionResourceName: n})
+		meta.AddAnnotations(m, map[string]string{xcrd.AnnotationKeyCompositionResourceName: n})
 	}
 }
 
diff --git a/internal/controller/apiextensions/activationpolicy/reconciler_test.go b/internal/controller/apiextensions/activationpolicy/reconciler_test.go
new file mode 100644
index 000000000..82586f947
--- /dev/null
+++ b/internal/controller/apiextensions/activationpolicy/reconciler_test.go
@@ -0,0 +1,482 @@
+/*
+Copyright 2025 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 activationpolicy
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/test"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+// Helper functions for creating test objects.
+type MRAPModifier func(mrap *v2alpha1.ManagedResourceActivationPolicy)
+
+func NewMRAP(m ...MRAPModifier) *v2alpha1.ManagedResourceActivationPolicy {
+	mrap := &v2alpha1.ManagedResourceActivationPolicy{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "test-mrap",
+		},
+		Spec: v2alpha1.ManagedResourceActivationPolicySpec{
+			Activations: []v2alpha1.ActivationPolicy{
+				"*.aws.crossplane.io",
+			},
+		},
+	}
+	for _, fn := range m {
+		fn(mrap)
+	}
+	return mrap
+}
+
+func WithMRAPDeletionTimestamp(t time.Time) MRAPModifier {
+	return func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+		mrap.SetDeletionTimestamp(&metav1.Time{Time: t})
+	}
+}
+
+func WithMRAPPaused() MRAPModifier {
+	return func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+		mrap.SetAnnotations(map[string]string{
+			meta.AnnotationKeyReconciliationPaused: "true",
+		})
+	}
+}
+
+func WithMRAPActivations(activations ...string) MRAPModifier {
+	return func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+		policies := make([]v2alpha1.ActivationPolicy, len(activations))
+		for i, a := range activations {
+			policies[i] = v2alpha1.ActivationPolicy(a)
+		}
+		mrap.Spec.Activations = policies
+	}
+}
+
+type MRDModifier func(mrd *v2alpha1.ManagedResourceDefinition)
+
+func NewMRD(name string, m ...MRDModifier) *v2alpha1.ManagedResourceDefinition {
+	mrd := &v2alpha1.ManagedResourceDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: name,
+		},
+		Spec: v2alpha1.ManagedResourceDefinitionSpec{
+			State: v2alpha1.ManagedResourceDefinitionInactive,
+		},
+	}
+	for _, fn := range m {
+		fn(mrd)
+	}
+	return mrd
+}
+
+func WithMRDState(state v2alpha1.ManagedResourceDefinitionState) MRDModifier {
+	return func(mrd *v2alpha1.ManagedResourceDefinition) {
+		mrd.Spec.State = state
+	}
+}
+
+// A get function that supplies the input resource.
+func WithMRAP(t *testing.T, mrap *v2alpha1.ManagedResourceActivationPolicy) func(context.Context, client.ObjectKey, client.Object) error {
+	t.Helper()
+	return func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+		t.Helper()
+		if o, ok := obj.(*v2alpha1.ManagedResourceActivationPolicy); ok {
+			*o = *mrap
+		}
+		return nil
+	}
+}
+
+// A status update function that ensures the supplied object is the MRAP we want.
+func WantMRAP(t *testing.T, want *v2alpha1.ManagedResourceActivationPolicy) func(context.Context, client.Object, ...client.SubResourceUpdateOption) error {
+	t.Helper()
+	return func(_ context.Context, got client.Object, _ ...client.SubResourceUpdateOption) error {
+		t.Helper()
+		if diff := cmp.Diff(want, got, cmpopts.EquateApproxTime(3*time.Second)); diff != "" {
+			t.Errorf("WantMRAP(...): -want, +got: %s", diff)
+		}
+		return nil
+	}
+}
+
+// A list function that supplies the input MRD list.
+func WithMRDList(t *testing.T, mrds ...*v2alpha1.ManagedResourceDefinition) func(context.Context, client.ObjectList, ...client.ListOption) error {
+	t.Helper()
+	return func(_ context.Context, obj client.ObjectList, _ ...client.ListOption) error {
+		t.Helper()
+		if o, ok := obj.(*v2alpha1.ManagedResourceDefinitionList); ok {
+			items := make([]v2alpha1.ManagedResourceDefinition, len(mrds))
+			for i, mrd := range mrds {
+				items[i] = *mrd
+			}
+			o.Items = items
+		}
+		return nil
+	}
+}
+
+// A patch function that validates the patch operation.
+func WantMRDPatch(t *testing.T, expectedPatches map[string]v2alpha1.ManagedResourceDefinitionState) func(context.Context, client.Object, client.Patch, ...client.PatchOption) error {
+	t.Helper()
+	return func(_ context.Context, obj client.Object, _ client.Patch, _ ...client.PatchOption) error {
+		t.Helper()
+		if mrd, ok := obj.(*v2alpha1.ManagedResourceDefinition); ok {
+			if expectedState, exists := expectedPatches[mrd.GetName()]; exists {
+				if mrd.Spec.State != expectedState {
+					t.Errorf("WantMRDPatch(...): expected state %s for MRD %s, got %s", expectedState, mrd.GetName(), mrd.Spec.State)
+				}
+			}
+		}
+		return nil
+	}
+}
+
+func TestReconcile(t *testing.T) {
+	errBoom := errors.New("boom")
+	now := metav1.Now()
+
+	type args struct {
+		c client.Client
+	}
+	type want struct {
+		r   reconcile.Result
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"MRAPNotFound": {
+			reason: "We should not return an error if the ManagedResourceActivationPolicy was not found.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
+				},
+			},
+			want: want{
+				r: reconcile.Result{Requeue: false},
+			},
+		},
+		"GetMRAPError": {
+			reason: "We should return error encountered while getting the ManagedResourceActivationPolicy.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errGetMRAP),
+			},
+		},
+		"MRAPBeingDeleted": {
+			reason: "We should update status to Terminating and return without error when MRAP is being deleted.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPDeletionTimestamp(now.Time))),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.SetDeletionTimestamp(&now)
+						mrap.SetConditions(v2alpha1.TerminatingActivationPolicy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRAPDeletionStatusUpdateError": {
+			reason: "We should requeue on conflict when updating status during deletion.",
+			args: args{
+				c: &test.MockClient{
+					MockGet:          WithMRAP(t, NewMRAP(WithMRAPDeletionTimestamp(now.Time))),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(kerrors.NewConflict(schema.GroupResource{}, "", errBoom)),
+				},
+			},
+			want: want{
+				r: reconcile.Result{Requeue: true},
+			},
+		},
+		"MRAPDeletionStatusUpdateErrorNonConflict": {
+			reason: "We should return error when status update fails during deletion with non-conflict error.",
+			args: args{
+				c: &test.MockClient{
+					MockGet:          WithMRAP(t, NewMRAP(WithMRAPDeletionTimestamp(now.Time))),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errUpdateStatus),
+			},
+		},
+		"ReconciliationPaused": {
+			reason: "We should return no error and no requeue when reconciliation is paused.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPPaused())),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"ListMRDError": {
+			reason: "We should return error and update status when listing MRDs fails.",
+			args: args{
+				c: &test.MockClient{
+					MockGet:  WithMRAP(t, NewMRAP()),
+					MockList: test.NewMockListFn(errBoom),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.SetConditions(v2alpha1.BlockedActivationPolicy().WithMessage(errListMRD))
+					})),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errListMRD),
+			},
+		},
+		"ListMRDErrorStatusUpdateConflict": {
+			reason: "We should requeue on conflict when updating status after list MRD error.",
+			args: args{
+				c: &test.MockClient{
+					MockGet:          WithMRAP(t, NewMRAP()),
+					MockList:         test.NewMockListFn(errBoom),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(kerrors.NewConflict(schema.GroupResource{}, "", errBoom)),
+				},
+			},
+			want: want{
+				r: reconcile.Result{Requeue: true},
+			},
+		},
+		"NoMRDsToActivate": {
+			reason: "We should succeed when no MRDs match the activation policy.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io"))),
+					MockList: WithMRDList(t,
+						NewMRD("database.gcp.crossplane.io"),
+						NewMRD("storage.azure.crossplane.io"),
+					),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{"*.aws.crossplane.io"}
+						mrap.Status.Activated = nil
+						mrap.SetConditions(v2alpha1.Healthy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"ActivateSingleMRD": {
+			reason: "We should activate a single MRD that matches the activation policy.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io"))),
+					MockList: WithMRDList(t,
+						NewMRD("bucket.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+						NewMRD("database.gcp.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+					),
+					MockPatch: WantMRDPatch(t, map[string]v2alpha1.ManagedResourceDefinitionState{
+						"bucket.aws.crossplane.io": v2alpha1.ManagedResourceDefinitionActive,
+					}),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{"*.aws.crossplane.io"}
+						mrap.Status.Activated = []string{"bucket.aws.crossplane.io"}
+						mrap.SetConditions(v2alpha1.Healthy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"ActivateMultipleMRDs": {
+			reason: "We should activate multiple MRDs that match the activation policy.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io"))),
+					MockList: WithMRDList(t,
+						NewMRD("bucket.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+						NewMRD("instance.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+						NewMRD("database.gcp.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+					),
+					MockPatch: WantMRDPatch(t, map[string]v2alpha1.ManagedResourceDefinitionState{
+						"bucket.aws.crossplane.io":   v2alpha1.ManagedResourceDefinitionActive,
+						"instance.aws.crossplane.io": v2alpha1.ManagedResourceDefinitionActive,
+					}),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{"*.aws.crossplane.io"}
+						mrap.Status.Activated = []string{"bucket.aws.crossplane.io", "instance.aws.crossplane.io"}
+						mrap.SetConditions(v2alpha1.Healthy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"SkipAlreadyActiveMRD": {
+			reason: "We should skip MRDs that are already active and include them in status.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io"))),
+					MockList: WithMRDList(t,
+						NewMRD("bucket.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionActive)),
+						NewMRD("instance.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+					),
+					MockPatch: WantMRDPatch(t, map[string]v2alpha1.ManagedResourceDefinitionState{
+						"instance.aws.crossplane.io": v2alpha1.ManagedResourceDefinitionActive,
+					}),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{"*.aws.crossplane.io"}
+						mrap.Status.Activated = []string{"bucket.aws.crossplane.io", "instance.aws.crossplane.io"}
+						mrap.SetConditions(v2alpha1.Healthy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"PartialActivationFailure": {
+			reason: "We should set unhealthy status when some MRDs fail to activate.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io"))),
+					MockList: WithMRDList(t,
+						NewMRD("bucket.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+						NewMRD("instance.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+					),
+					MockPatch: func(_ context.Context, obj client.Object, _ client.Patch, _ ...client.PatchOption) error {
+						if mrd, ok := obj.(*v2alpha1.ManagedResourceDefinition); ok {
+							if mrd.GetName() == "bucket.aws.crossplane.io" {
+								return errBoom
+							}
+						}
+						return nil
+					},
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{"*.aws.crossplane.io"}
+						mrap.Status.Activated = []string{"instance.aws.crossplane.io"}
+						mrap.SetConditions(v2alpha1.Unhealthy().WithMessage("failed to activate 1 of 1 ManagedResourceDefinitions"))
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MultipleActivationPolicies": {
+			reason: "We should activate MRDs matching any of multiple activation policies.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io", "*.gcp.crossplane.io"))),
+					MockList: WithMRDList(t,
+						NewMRD("bucket.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+						NewMRD("storage.gcp.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+						NewMRD("database.azure.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+					),
+					MockPatch: WantMRDPatch(t, map[string]v2alpha1.ManagedResourceDefinitionState{
+						"bucket.aws.crossplane.io":  v2alpha1.ManagedResourceDefinitionActive,
+						"storage.gcp.crossplane.io": v2alpha1.ManagedResourceDefinitionActive,
+					}),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{"*.aws.crossplane.io", "*.gcp.crossplane.io"}
+						mrap.Status.Activated = []string{"bucket.aws.crossplane.io", "storage.gcp.crossplane.io"}
+						mrap.SetConditions(v2alpha1.Healthy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"EmptyActivationPolicies": {
+			reason: "We should succeed with empty status when no activation policies are defined.",
+			args: args{
+				c: &test.MockClient{
+					MockGet: WithMRAP(t, NewMRAP(WithMRAPActivations())),
+					MockList: WithMRDList(t,
+						NewMRD("bucket.aws.crossplane.io", WithMRDState(v2alpha1.ManagedResourceDefinitionInactive)),
+					),
+					MockStatusUpdate: WantMRAP(t, NewMRAP(func(mrap *v2alpha1.ManagedResourceActivationPolicy) {
+						mrap.Spec.Activations = []v2alpha1.ActivationPolicy{}
+						mrap.Status.Activated = nil
+						mrap.SetConditions(v2alpha1.Healthy())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"StatusUpdateError": {
+			reason: "We should return error when final status update fails.",
+			args: args{
+				c: &test.MockClient{
+					MockGet:          WithMRAP(t, NewMRAP(WithMRAPActivations("*.aws.crossplane.io"))),
+					MockList:         WithMRDList(t),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, errUpdateStatus),
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			r := &Reconciler{
+				Client:     tc.args.c,
+				log:        logging.NewNopLogger(),
+				record:     event.NewNopRecorder(),
+				conditions: conditions.ObservedGenerationPropagationManager{},
+			}
+
+			got, err := r.Reconcile(context.Background(), reconcile.Request{
+				NamespacedName: types.NamespacedName{Name: "test-mrap"},
+			})
+
+			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)
+			}
+		})
+	}
+}
diff --git a/internal/controller/apiextensions/composite/composition_functions_test.go b/internal/controller/apiextensions/composite/composition_functions_test.go
index be4128222..69a893720 100644
--- a/internal/controller/apiextensions/composite/composition_functions_test.go
+++ b/internal/controller/apiextensions/composite/composition_functions_test.go
@@ -49,6 +49,7 @@ import (
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
 	"github.com/crossplane/crossplane/internal/xcrd"
+	"github.com/crossplane/crossplane/internal/xfn"
 	fnv1 "github.com/crossplane/crossplane/proto/fn/v1"
 )
 
@@ -56,6 +57,7 @@ func TestFunctionCompose(t *testing.T) {
 	errBoom := errors.New("boom")
 
 	errProtoSyntax := protojson.Unmarshal([]byte("hi"), &structpb.Struct{})
+	errFmtFetchBootstrapRequirements := "cannot fetch bootstrap required resources for requirement %q"
 
 	type params struct {
 		c  client.Client
@@ -916,6 +918,49 @@ func TestFunctionCompose(t *testing.T) {
 				err: errors.Wrapf(errBoom, errFmtApplyCD, "uncool-resource"),
 			},
 		},
+		"BootstrapRequirementsError": {
+			reason: "We should return an error if we can't fetch bootstrap requirements",
+			params: params{
+				c: &test.MockClient{},
+				r: FunctionRunnerFn(func(_ context.Context, _ string, _ *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) {
+					return &fnv1.RunFunctionResponse{}, nil
+				}),
+				o: []FunctionComposerOption{
+					WithRequiredResourcesFetcher(xfn.RequiredResourcesFetcherFn(func(_ context.Context, _ *fnv1.ResourceSelector) (*fnv1.Resources, error) {
+						return nil, errBoom
+					})),
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				xr:  WithParentLabel(),
+				req: CompositionRequest{
+					Revision: &v1.CompositionRevision{
+						Spec: v1.CompositionRevisionSpec{
+							Pipeline: []v1.PipelineStep{
+								{
+									Step:        "cool-step",
+									FunctionRef: v1.FunctionReference{Name: "cool-function"},
+									Requirements: &v1.FunctionRequirements{
+										RequiredResources: []v1.RequiredResourceSelector{
+											{
+												RequirementName: "test-requirement",
+												APIVersion:      "v1",
+												Kind:            "ConfigMap",
+												Name:            ptr.To("test-config"),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				err: errors.Wrapf(errBoom, errFmtFetchBootstrapRequirements, "test-requirement"),
+			},
+		},
 		"Successful": {
 			reason: "We should return a valid CompositionResult when a 'pure Function' (i.e. patch-and-transform-less) reconcile succeeds",
 			params: params{
@@ -1143,6 +1188,107 @@ func TestFunctionCompose(t *testing.T) {
 				err: nil,
 			},
 		},
+		"ResourceReferencesWithoutObservedResources": {
+			reason: "When XR has resourceRefs but the actual resources don't exist, the function should use a deterministic name (same as resourceRefs).",
+			params: params{
+				c: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "Deployment"}, "")), // all names are available
+					MockPatch: test.NewMockPatchFn(nil, func(obj client.Object) error {
+						// Check if the composed resource uses the expected name from resourceRefs
+						if cd, ok := obj.(*composed.Unstructured); ok {
+							// This test demonstrates the bug: the composed resource should use "existing-deployment-name" from resourceRefs,
+							// but currently it generates a new name instead
+							if cd.GetName() != "parent-xr-f4d3ac8501550bf84c63a814cc31ecb3161cbb88370f-test-res" {
+								// This is the current buggy behavior - it generates a new name instead of using the existing one
+								// Log this for debugging when we run the test
+								return errors.Errorf("BUG: Composed resource generated new name %s instead of using existing name from resourceRefs: parent-xr-f4d3ac8501550bf84c63a814cc31ecb3161cbb88370f-test-res", cd.GetName())
+							}
+						}
+						return nil
+					}),
+					MockStatusPatch: test.NewMockSubResourcePatchFn(nil),
+				},
+				uc: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "secrets"}, "")),
+				},
+				r: FunctionRunnerFn(func(_ context.Context, _ string, _ *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) {
+					// Function returns a desired resource with the same name as referenced
+					rsp := &fnv1.RunFunctionResponse{
+						Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(5 * time.Minute)},
+						Desired: &fnv1.State{
+							Resources: map[string]*fnv1.Resource{
+								"test-resource-with-a-super-duper-really-long-name-to-test-compaction": {
+									Resource: MustStruct(map[string]any{
+										"apiVersion": "apps/v1",
+										"kind":       "Deployment",
+										"metadata":   map[string]any{},
+										"spec": map[string]any{
+											"replicas": 1,
+										},
+									}),
+								},
+							},
+						},
+					}
+					return rsp, nil
+				}),
+				o: []FunctionComposerOption{
+					WithCompositeConnectionDetailsFetcher(ConnectionDetailsFetcherFn(func(_ context.Context, _ ConnectionSecretOwner) (managed.ConnectionDetails, error) {
+						return nil, nil
+					})),
+					WithComposedResourceObserver(ComposedResourceObserverFn(func(_ context.Context, _ resource.Composite) (ComposedResourceStates, error) {
+						// Return empty observed resources - simulating that the resources don't exist in cluster
+						return ComposedResourceStates{}, nil
+					})),
+					WithComposedResourceGarbageCollector(ComposedResourceGarbageCollectorFn(func(_ context.Context, _ metav1.Object, _, _ ComposedResourceStates) error {
+						return nil
+					})),
+				},
+			},
+			args: args{
+				xr: func() *composite.Unstructured {
+					xr := composite.New(composite.WithGroupVersionKind(schema.GroupVersionKind{
+						Group:   "test.crossplane.io",
+						Version: "v1",
+						Kind:    "CoolComposite",
+					}))
+					xr.SetLabels(map[string]string{
+						xcrd.LabelKeyNamePrefixForComposed: "parent-xr",
+					})
+					xr.SetUID("75e4a668-035f-4ce8-8c45-f4d3ac850155")
+					// Set resource references that exist from a previous reconciliation
+					xr.SetResourceReferences([]corev1.ObjectReference{
+						{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+							Name:       "parent-xr-f4d3ac8501550bf84c63a814cc31ecb3161cbb88370f-test-res",
+						},
+					})
+					return xr
+				}(),
+				req: CompositionRequest{
+					Revision: &v1.CompositionRevision{
+						Spec: v1.CompositionRevisionSpec{
+							Pipeline: []v1.PipelineStep{
+								{
+									Step:        "run-cool-function",
+									FunctionRef: v1.FunctionReference{Name: "cool-function"},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				res: CompositionResult{
+					Composed: []ComposedResource{
+						{ResourceName: "test-resource-with-a-super-duper-really-long-name-to-test-compaction", Synced: true},
+					},
+					TTL: 5 * time.Minute,
+				},
+				err: nil,
+			},
+		},
 	}
 
 	for name, tc := range cases {
@@ -1172,6 +1318,111 @@ func MustStruct(v map[string]any) *structpb.Struct {
 	return s
 }
 
+func TestToProtobufResourceSelector(t *testing.T) {
+	type args struct {
+		r v1.RequiredResourceSelector
+	}
+	type want struct {
+		result *fnv1.ResourceSelector
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"BasicSelector": {
+			reason: "Should convert basic API selector to protobuf selector",
+			args: args{
+				r: v1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "ConfigMap",
+				},
+			},
+			want: want{
+				result: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "ConfigMap",
+				},
+			},
+		},
+		"NameSelector": {
+			reason: "Should convert name-based selector",
+			args: args{
+				r: v1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "ConfigMap",
+					Name:            ptr.To("test-name"),
+				},
+			},
+			want: want{
+				result: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "ConfigMap",
+					Match: &fnv1.ResourceSelector_MatchName{
+						MatchName: "test-name",
+					},
+				},
+			},
+		},
+		"LabelSelector": {
+			reason: "Should convert label-based selector",
+			args: args{
+				r: v1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "ConfigMap",
+					MatchLabels:     map[string]string{"app": "test"},
+				},
+			},
+			want: want{
+				result: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "ConfigMap",
+					Match: &fnv1.ResourceSelector_MatchLabels{
+						MatchLabels: &fnv1.MatchLabels{
+							Labels: map[string]string{"app": "test"},
+						},
+					},
+				},
+			},
+		},
+		"NamespacedSelector": {
+			reason: "Should convert namespaced selector",
+			args: args{
+				r: v1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "ConfigMap",
+					Namespace:       ptr.To("test-namespace"),
+					Name:            ptr.To("test-name"),
+				},
+			},
+			want: want{
+				result: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "ConfigMap",
+					Namespace:  ptr.To("test-namespace"),
+					Match: &fnv1.ResourceSelector_MatchName{
+						MatchName: "test-name",
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			result := ToProtobufResourceSelector(tc.args.r)
+			if diff := cmp.Diff(tc.want.result, result, protocmp.Transform()); diff != "" {
+				t.Errorf("\n%s\nToProtobufResourceSelector(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
 func WithParentLabel() *composite.Unstructured {
 	xr := composite.New()
 	xr.SetLabels(map[string]string{xcrd.LabelKeyNamePrefixForComposed: "parent-xr"})
@@ -1343,7 +1594,7 @@ func TestGetComposedResources(t *testing.T) {
 				c: &test.MockClient{
 					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 						obj.SetName("cool-resource-42")
-						SetCompositionResourceName(obj, "cool-resource")
+						xcrd.SetCompositionResourceName(obj, "cool-resource")
 						return nil
 					}),
 				},
@@ -1381,7 +1632,7 @@ func TestGetComposedResources(t *testing.T) {
 				uc: &test.MockClient{
 					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 						obj.SetName("cool-resource-42")
-						SetCompositionResourceName(obj, "cool-resource")
+						xcrd.SetCompositionResourceName(obj, "cool-resource")
 						return nil
 					}),
 				},
@@ -1411,7 +1662,7 @@ func TestGetComposedResources(t *testing.T) {
 							cd.SetAPIVersion("example.org/v1")
 							cd.SetKind("Composed")
 							cd.SetName("cool-resource-42")
-							SetCompositionResourceName(cd, "cool-resource")
+							xcrd.SetCompositionResourceName(cd, "cool-resource")
 							return cd
 						}(),
 					},
@@ -1424,7 +1675,7 @@ func TestGetComposedResources(t *testing.T) {
 				c: &test.MockClient{
 					MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 						obj.SetName("cool-resource-42")
-						SetCompositionResourceName(obj, "cool-resource")
+						xcrd.SetCompositionResourceName(obj, "cool-resource")
 						return nil
 					}),
 				},
@@ -1459,7 +1710,7 @@ func TestGetComposedResources(t *testing.T) {
 							cd.SetAPIVersion("example.org/v1")
 							cd.SetKind("Composed")
 							cd.SetName("cool-resource-42")
-							SetCompositionResourceName(cd, "cool-resource")
+							xcrd.SetCompositionResourceName(cd, "cool-resource")
 							return cd
 						}(),
 					},
diff --git a/internal/controller/apiextensions/managed/reconciler_test.go b/internal/controller/apiextensions/managed/reconciler_test.go
new file mode 100644
index 000000000..261e6e5fc
--- /dev/null
+++ b/internal/controller/apiextensions/managed/reconciler_test.go
@@ -0,0 +1,773 @@
+/*
+Copyright 2025 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 managed
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
+	"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/test"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+func TestReconcile(t *testing.T) {
+	errBoom := errors.New("boom")
+	now := metav1.Now()
+
+	type args struct {
+		c    client.Client
+		opts []ReconcilerOption
+	}
+	type want struct {
+		r   reconcile.Result
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"ResourceNotFound": {
+			reason: "We should not return an error if the ManagedResourceDefinition was not found",
+			args: args{
+				c: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"GetMRDError": {
+			reason: "We should return error encountered while getting the ManagedResourceDefinition",
+			args: args{
+				c: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, "cannot get ManagedResourceDefinition"),
+			},
+		},
+		"MRDBeingDeleted": {
+			reason: "We should handle MRD deletion gracefully",
+			args: args{
+				c: &test.MockClient{
+					MockGet: withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.SetDeletionTimestamp(&now)
+					})),
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.SetDeletionTimestamp(&now)
+						mrd.SetConditions(v2alpha1.TerminatingManaged())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRDBeingDeletedStatusUpdateError": {
+			reason: "We should handle status update errors during deletion",
+			args: args{
+				c: &test.MockClient{
+					MockGet: withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.SetDeletionTimestamp(&now)
+					})),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, "cannot update status of ManagedResourceDefinition"),
+			},
+		},
+		"MRDBeingDeletedStatusUpdateConflict": {
+			reason: "We should requeue on status update conflicts during deletion",
+			args: args{
+				c: &test.MockClient{
+					MockGet: withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.SetDeletionTimestamp(&now)
+					})),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(kerrors.NewConflict(schema.GroupResource{}, "test", errBoom)),
+				},
+			},
+			want: want{
+				r: reconcile.Result{Requeue: true},
+			},
+		},
+		"MRDPaused": {
+			reason: "We should not reconcile a paused MRD",
+			args: args{
+				c: &test.MockClient{
+					MockGet: withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.SetAnnotations(map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRDInactiveState": {
+			reason: "We should mark MRD as inactive when state is not active",
+			args: args{
+				c: &test.MockClient{
+					MockGet: withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionInactive
+					})),
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionInactive
+						mrd.SetConditions(v2alpha1.InactiveManaged())
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRDActiveCRDReconcileError": {
+			reason: "We should handle CRD reconcile errors",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+							}))(ctx, key, obj)
+						case *extv1.CustomResourceDefinition:
+							return errBoom
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.SetConditions(v2alpha1.BlockedManaged().WithMessage("unable to reconcile CustomResourceDefinition, see events"))
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errors.Wrap(errBoom, "cannot get CustomResourceDefinition"), "cannot reconcile CustomResourceDefinition"),
+			},
+		},
+		"MRDActiveCRDCreateSuccess": {
+			reason: "We should create CRD when it doesn't exist",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch o := obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+								mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+									Group: "example.com",
+									Names: extv1.CustomResourceDefinitionNames{
+										Plural: "databases",
+										Kind:   "Database",
+									},
+									Scope: extv1.ClusterScoped,
+									Versions: []v2alpha1.CustomResourceDefinitionVersion{
+										{
+											Name:    "v1",
+											Served:  true,
+											Storage: true,
+											Schema: &v2alpha1.CustomResourceValidation{
+												OpenAPIV3Schema: runtime.RawExtension{
+													Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+												},
+											},
+										},
+									},
+								}
+							}))(ctx, key, o)
+						case *extv1.CustomResourceDefinition:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockCreate: test.NewMockCreateFn(nil),
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+										},
+									},
+								},
+							},
+						}
+						mrd.SetConditions(v2alpha1.PendingManaged())
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRDActiveCRDCreateError": {
+			reason: "We should handle CRD creation errors",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch o := obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+								mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+									Group: "example.com",
+									Names: extv1.CustomResourceDefinitionNames{
+										Plural: "databases",
+										Kind:   "Database",
+									},
+									Scope: extv1.ClusterScoped,
+									Versions: []v2alpha1.CustomResourceDefinitionVersion{
+										{
+											Name:    "v1",
+											Served:  true,
+											Storage: true,
+											Schema: &v2alpha1.CustomResourceValidation{
+												OpenAPIV3Schema: runtime.RawExtension{
+													Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+												},
+											},
+										},
+									},
+								}
+							}))(ctx, key, o)
+						case *extv1.CustomResourceDefinition:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockCreate: test.NewMockCreateFn(errBoom),
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+										},
+									},
+								},
+							},
+						}
+						mrd.SetConditions(v2alpha1.BlockedManaged().WithMessage("unable to reconcile CustomResourceDefinition, see events"))
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errors.Wrap(errBoom, "cannot create CustomResourceDefinition"), "cannot reconcile CustomResourceDefinition"),
+			},
+		},
+		"MRDActiveCRDUpdateSuccess": {
+			reason: "We should update CRD when it exists but is outdated",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch o := obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+								mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+									Group: "example.com",
+									Names: extv1.CustomResourceDefinitionNames{
+										Plural: "databases",
+										Kind:   "Database",
+									},
+									Scope: extv1.ClusterScoped,
+									Versions: []v2alpha1.CustomResourceDefinitionVersion{
+										{
+											Name:    "v1",
+											Served:  true,
+											Storage: true,
+											Schema: &v2alpha1.CustomResourceValidation{
+												OpenAPIV3Schema: runtime.RawExtension{
+													Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+												},
+											},
+										},
+									},
+								}
+							}))(ctx, key, o)
+						case *extv1.CustomResourceDefinition:
+							o.Name = key.Name
+							o.Spec = extv1.CustomResourceDefinitionSpec{
+								Group: "oldexample.com", // Different from MRD to force update
+								Names: extv1.CustomResourceDefinitionNames{
+									Plural: "olddatabases",
+									Kind:   "OldDatabase",
+								},
+								Scope: extv1.ClusterScoped,
+								Versions: []extv1.CustomResourceDefinitionVersion{
+									{
+										Name:    "v1beta1",
+										Served:  false,
+										Storage: false,
+									},
+								},
+							}
+							return nil
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockUpdate: test.NewMockUpdateFn(nil),
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+										},
+									},
+								},
+							},
+						}
+						mrd.SetConditions(v2alpha1.PendingManaged())
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRDActiveCRDUpdateError": {
+			reason: "We should handle CRD update errors",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch o := obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+								mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+									Group: "example.com",
+									Names: extv1.CustomResourceDefinitionNames{
+										Plural: "databases",
+										Kind:   "Database",
+									},
+									Scope: extv1.ClusterScoped,
+									Versions: []v2alpha1.CustomResourceDefinitionVersion{
+										{
+											Name:    "v1",
+											Served:  true,
+											Storage: true,
+											Schema: &v2alpha1.CustomResourceValidation{
+												OpenAPIV3Schema: runtime.RawExtension{
+													Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+												},
+											},
+										},
+									},
+								}
+							}))(ctx, key, o)
+						case *extv1.CustomResourceDefinition:
+							o.Name = key.Name
+							o.Spec = extv1.CustomResourceDefinitionSpec{
+								Group: "oldexample.com", // Different from MRD to force update
+								Names: extv1.CustomResourceDefinitionNames{
+									Plural: "olddatabases",
+									Kind:   "OldDatabase",
+								},
+								Scope: extv1.ClusterScoped,
+								Versions: []extv1.CustomResourceDefinitionVersion{
+									{
+										Name:    "v1beta1",
+										Served:  false,
+										Storage: false,
+									},
+								},
+							}
+							return nil
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockUpdate: test.NewMockUpdateFn(errBoom),
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+										},
+									},
+								},
+							},
+						}
+						mrd.SetConditions(v2alpha1.BlockedManaged().WithMessage("unable to reconcile CustomResourceDefinition, see events"))
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errors.Wrap(errBoom, "cannot update CustomResourceDefinition"), "cannot reconcile CustomResourceDefinition"),
+			},
+		},
+		"MRDActiveCRDEstablished": {
+			reason: "We should mark MRD as established when CRD is established",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch o := obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+								mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+									Group: "example.com",
+									Names: extv1.CustomResourceDefinitionNames{
+										Plural: "databases",
+										Kind:   "Database",
+									},
+									Scope: extv1.ClusterScoped,
+									Versions: []v2alpha1.CustomResourceDefinitionVersion{
+										{
+											Name:    "v1",
+											Served:  true,
+											Storage: true,
+											Schema: &v2alpha1.CustomResourceValidation{
+												OpenAPIV3Schema: runtime.RawExtension{
+													Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+												},
+											},
+										},
+									},
+								}
+							}))(ctx, key, o)
+						case *extv1.CustomResourceDefinition:
+							o.Name = key.Name
+							o.Spec = extv1.CustomResourceDefinitionSpec{
+								Group: "example.com",
+								Names: extv1.CustomResourceDefinitionNames{
+									Plural: "databases",
+									Kind:   "Database",
+								},
+								Scope: extv1.ClusterScoped,
+								Versions: []extv1.CustomResourceDefinitionVersion{
+									{
+										Name:    "v1",
+										Served:  true,
+										Storage: true,
+										Schema: &extv1.CustomResourceValidation{
+											OpenAPIV3Schema: &extv1.JSONSchemaProps{
+												Type: "object",
+												Properties: map[string]extv1.JSONSchemaProps{
+													"spec": {
+														Type: "object",
+													},
+												},
+											},
+										},
+									},
+								},
+							}
+							// Set established condition
+							o.Status.Conditions = []extv1.CustomResourceDefinitionCondition{
+								{
+									Type:   extv1.Established,
+									Status: extv1.ConditionTrue,
+								},
+							}
+							return nil
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+										},
+									},
+								},
+							},
+						}
+						mrd.SetConditions(v2alpha1.EstablishedManaged())
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"MRDActiveCRDDeleted": {
+			reason: "We should handle CRD deletion errors",
+			args: args{
+				c: &test.MockClient{
+					MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+						switch o := obj.(type) {
+						case *v2alpha1.ManagedResourceDefinition:
+							return withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+								mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+								mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+									Group: "example.com",
+									Names: extv1.CustomResourceDefinitionNames{
+										Plural: "databases",
+										Kind:   "Database",
+									},
+									Scope: extv1.ClusterScoped,
+									Versions: []v2alpha1.CustomResourceDefinitionVersion{
+										{
+											Name:    "v1",
+											Served:  true,
+											Storage: true,
+											Schema: &v2alpha1.CustomResourceValidation{
+												OpenAPIV3Schema: runtime.RawExtension{
+													Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+												},
+											},
+										},
+									},
+								}
+							}))(ctx, key, o)
+						case *extv1.CustomResourceDefinition:
+							o.Name = key.Name
+							o.SetDeletionTimestamp(&now)
+							return nil
+						default:
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+					},
+					MockStatusUpdate: wantMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionActive
+						mrd.Spec.CustomResourceDefinitionSpec = v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+										},
+									},
+								},
+							},
+						}
+						mrd.SetConditions(v2alpha1.BlockedManaged().WithMessage("unable to reconcile CustomResourceDefinition, see events"))
+					})),
+				},
+				opts: []ReconcilerOption{
+					WithRecorder(&testRecorder{}),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errors.New("crd was deleted"), "cannot reconcile CustomResourceDefinition"),
+			},
+		},
+		"StatusUpdateError": {
+			reason: "We should handle status update errors",
+			args: args{
+				c: &test.MockClient{
+					MockGet: withMRD(t, newMRD(func(mrd *v2alpha1.ManagedResourceDefinition) {
+						mrd.Spec.State = v2alpha1.ManagedResourceDefinitionInactive
+					})),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(errBoom),
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, "cannot update status of ManagedResourceDefinition"),
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			r := &Reconciler{
+				Client:     tc.args.c,
+				log:        logging.NewNopLogger(),
+				record:     event.NewNopRecorder(),
+				conditions: conditions.ObservedGenerationPropagationManager{},
+			}
+
+			for _, opt := range tc.args.opts {
+				opt(r)
+			}
+
+			got, err := r.Reconcile(context.Background(), reconcile.Request{
+				NamespacedName: types.NamespacedName{Name: "test-mrd"},
+			})
+
+			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)
+			}
+		})
+	}
+}
+
+// Helper functions and types for testing
+
+type mrdModifier func(mrd *v2alpha1.ManagedResourceDefinition)
+
+func newMRD(m ...mrdModifier) *v2alpha1.ManagedResourceDefinition {
+	mrd := &v2alpha1.ManagedResourceDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "test-mrd",
+			UID:  types.UID("test-uid"),
+		},
+		Spec: v2alpha1.ManagedResourceDefinitionSpec{
+			State: v2alpha1.ManagedResourceDefinitionActive,
+		},
+	}
+	for _, fn := range m {
+		fn(mrd)
+	}
+	return mrd
+}
+
+// withMRD returns a MockGetFn that supplies the input ManagedResourceDefinition.
+func withMRD(_ *testing.T, mrd *v2alpha1.ManagedResourceDefinition) func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+	return func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+		if o, ok := obj.(*v2alpha1.ManagedResourceDefinition); ok {
+			*o = *mrd
+		}
+		return nil
+	}
+}
+
+// wantMRD returns a MockStatusUpdateFn that ensures the supplied object is the MRD we want.
+func wantMRD(t *testing.T, want *v2alpha1.ManagedResourceDefinition) func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
+	t.Helper()
+	return func(_ context.Context, got client.Object, _ ...client.SubResourceUpdateOption) error {
+		t.Helper()
+		if diff := cmp.Diff(want, got, cmpopts.EquateApproxTime(3*time.Second)); diff != "" {
+			t.Errorf("wantMRD(...): -want, +got: %s", diff)
+		}
+		return nil
+	}
+}
+
+// testRecorder allows asserting event creation.
+type testRecorder struct {
+	events []event.Event
+}
+
+func (r *testRecorder) Event(_ runtime.Object, e event.Event) {
+	r.events = append(r.events, e)
+}
+
+func (r *testRecorder) WithAnnotations(_ ...string) event.Recorder {
+	return r
+}
diff --git a/internal/controller/apiextensions/managed/resources/custom_resource_definition_test.go b/internal/controller/apiextensions/managed/resources/custom_resource_definition_test.go
new file mode 100644
index 000000000..a3e856bd5
--- /dev/null
+++ b/internal/controller/apiextensions/managed/resources/custom_resource_definition_test.go
@@ -0,0 +1,1083 @@
+/*
+Copyright 2025 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 resources
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/utils/ptr"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+func TestEmptyCustomResourceDefinition(t *testing.T) {
+	type args struct {
+		mrd *v2alpha1.ManagedResourceDefinition
+	}
+	type want struct {
+		crd *extv1.CustomResourceDefinition
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"BasicMRD": {
+			reason: "Should create an empty CRD with the same name as the MRD",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-mrd",
+					},
+				},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-mrd",
+					},
+				},
+			},
+		},
+		"MRDWithNamespace": {
+			reason: "Should create an empty CRD with the same name as the MRD, ignoring namespace",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "namespaced-mrd",
+						Namespace: "some-namespace",
+					},
+				},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "namespaced-mrd",
+					},
+				},
+			},
+		},
+		"MRDWithComplexName": {
+			reason: "Should handle complex names correctly",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "databases.example.com",
+					},
+				},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "databases.example.com",
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := EmptyCustomResourceDefinition(tc.args.mrd)
+			if diff := cmp.Diff(tc.want.crd, got); diff != "" {
+				t.Errorf("\n%s\nEmptyCustomResourceDefinition(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestMergeCustomResourceDefinitionInto(t *testing.T) {
+	validSchema := &v2alpha1.CustomResourceValidation{
+		OpenAPIV3Schema: runtime.RawExtension{
+			Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
+		},
+	}
+	invalidSchema := &v2alpha1.CustomResourceValidation{
+		OpenAPIV3Schema: runtime.RawExtension{
+			Raw: []byte(`{invalid json`),
+		},
+	}
+
+	type args struct {
+		mrd *v2alpha1.ManagedResourceDefinition
+		crd *extv1.CustomResourceDefinition
+	}
+	type want struct {
+		crd *extv1.CustomResourceDefinition
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"BasicMerge": {
+			reason: "Should merge basic CRD spec fields from MRD",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural:   "databases",
+								Singular: "database",
+								Kind:     "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "databases.example.com",
+					},
+				},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "databases.example.com",
+					},
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural:   "databases",
+							Singular: "database",
+							Kind:     "Database",
+						},
+						Scope: extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"MultipleVersions": {
+			reason: "Should handle multiple versions and sort them alphabetically",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.NamespaceScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v2",
+									Served:  true,
+									Storage: false,
+									Schema:  validSchema,
+								},
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+								},
+								{
+									Name:    "v1beta1",
+									Served:  false,
+									Storage: false,
+									Schema:  validSchema,
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope: extv1.NamespaceScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+							{
+								Name:    "v1beta1",
+								Served:  false,
+								Storage: false,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+							{
+								Name:    "v2",
+								Served:  true,
+								Storage: false,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"WithConversion": {
+			reason: "Should handle conversion settings",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Conversion: &extv1.CustomResourceConversion{
+								Strategy: extv1.WebhookConverter,
+							},
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope: extv1.ClusterScoped,
+						Conversion: &extv1.CustomResourceConversion{
+							Strategy: extv1.WebhookConverter,
+						},
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"WithDeprecation": {
+			reason: "Should handle deprecated versions with warning",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:               "v1",
+									Served:             true,
+									Storage:            true,
+									Deprecated:         true,
+									DeprecationWarning: ptr.To("This version is deprecated"),
+									Schema:             validSchema,
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope: extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:               "v1",
+								Served:             true,
+								Storage:            true,
+								Deprecated:         true,
+								DeprecationWarning: ptr.To("This version is deprecated"),
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"WithSubresources": {
+			reason: "Should handle subresources",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+									Subresources: &extv1.CustomResourceSubresources{
+										Status: &extv1.CustomResourceSubresourceStatus{},
+									},
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope: extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+								Subresources: &extv1.CustomResourceSubresources{
+									Status: &extv1.CustomResourceSubresourceStatus{},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"WithAdditionalPrinterColumns": {
+			reason: "Should handle additional printer columns",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+									AdditionalPrinterColumns: []extv1.CustomResourceColumnDefinition{
+										{
+											Name:        "Status",
+											Type:        "string",
+											JSONPath:    ".status.phase",
+											Description: "Status of the database",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope: extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+								AdditionalPrinterColumns: []extv1.CustomResourceColumnDefinition{
+									{
+										Name:        "Status",
+										Type:        "string",
+										JSONPath:    ".status.phase",
+										Description: "Status of the database",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"WithSelectableFields": {
+			reason: "Should handle selectable fields",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+									SelectableFields: []extv1.SelectableField{
+										{
+											JSONPath: ".spec.engine",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope: extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+								SelectableFields: []extv1.SelectableField{
+									{
+										JSONPath: ".spec.engine",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"WithPreserveUnknownFields": {
+			reason: "Should handle preserve unknown fields",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope:                 extv1.ClusterScoped,
+							PreserveUnknownFields: true,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope:                 extv1.ClusterScoped,
+						PreserveUnknownFields: true,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"InvalidSchemaError": {
+			reason: "Should return an error if schema conversion fails",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope: extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema:  invalidSchema,
+								},
+							},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope:    extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{{}},
+					},
+				},
+				err: errors.Wrap(errors.New("invalid character 'i' looking for beginning of object key string"), errParseValidation),
+			},
+		},
+		"EmptyVersions": {
+			reason: "Should handle empty versions list",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural: "databases",
+								Kind:   "Database",
+							},
+							Scope:    extv1.ClusterScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{},
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "example.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural: "databases",
+							Kind:   "Database",
+						},
+						Scope:    extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{},
+					},
+				},
+			},
+		},
+		"MergeIntoPreviousValues": {
+			reason: "Should merge into and replace existing CRD values",
+			args: args{
+				mrd: &v2alpha1.ManagedResourceDefinition{
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "newgroup.com",
+							Names: extv1.CustomResourceDefinitionNames{
+								Plural:   "newdatabases",
+								Singular: "newdatabase",
+								Kind:     "NewDatabase",
+							},
+							Scope: extv1.NamespaceScoped,
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v2",
+									Served:  true,
+									Storage: true,
+									Schema:  validSchema,
+								},
+							},
+							PreserveUnknownFields: true,
+						},
+					},
+				},
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        "olddatabases.oldgroup.com",
+						Annotations: map[string]string{"existing": "annotation"},
+					},
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "oldgroup.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural:   "olddatabases",
+							Singular: "olddatabase",
+							Kind:     "OldDatabase",
+						},
+						Scope: extv1.ClusterScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v1beta1",
+								Served:  false,
+								Storage: false,
+							},
+						},
+						PreserveUnknownFields: false,
+					},
+				},
+			},
+			want: want{
+				crd: &extv1.CustomResourceDefinition{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        "olddatabases.oldgroup.com",
+						Annotations: map[string]string{"existing": "annotation"},
+					},
+					Spec: extv1.CustomResourceDefinitionSpec{
+						Group: "newgroup.com",
+						Names: extv1.CustomResourceDefinitionNames{
+							Plural:   "newdatabases",
+							Singular: "newdatabase",
+							Kind:     "NewDatabase",
+						},
+						Scope: extv1.NamespaceScoped,
+						Versions: []extv1.CustomResourceDefinitionVersion{
+							{
+								Name:    "v2",
+								Served:  true,
+								Storage: true,
+								Schema: &extv1.CustomResourceValidation{
+									OpenAPIV3Schema: &extv1.JSONSchemaProps{
+										Type: "object",
+										Properties: map[string]extv1.JSONSchemaProps{
+											"spec": {
+												Type: "object",
+											},
+										},
+									},
+								},
+							},
+						},
+						PreserveUnknownFields: true,
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			err := MergeCustomResourceDefinitionInto(tc.args.mrd, tc.args.crd)
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nMergeCustomResourceDefinitionInto(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.crd, tc.args.crd); diff != "" {
+				t.Errorf("\n%s\nMergeCustomResourceDefinitionInto(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestToCustomResourceValidation(t *testing.T) {
+	validJSON := `{"type": "object", "properties": {"spec": {"type": "object"}}}`
+	invalidJSON := `{invalid json`
+
+	type args struct {
+		given *v2alpha1.CustomResourceValidation
+	}
+	type want struct {
+		validation *extv1.CustomResourceValidation
+		err        error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"ValidSchema": {
+			reason: "Should successfully convert valid schema",
+			args: args{
+				given: &v2alpha1.CustomResourceValidation{
+					OpenAPIV3Schema: runtime.RawExtension{
+						Raw: []byte(validJSON),
+					},
+				},
+			},
+			want: want{
+				validation: &extv1.CustomResourceValidation{
+					OpenAPIV3Schema: &extv1.JSONSchemaProps{
+						Type: "object",
+						Properties: map[string]extv1.JSONSchemaProps{
+							"spec": {
+								Type: "object",
+							},
+						},
+					},
+				},
+			},
+		},
+		"ComplexSchema": {
+			reason: "Should handle complex schema with multiple properties",
+			args: args{
+				given: &v2alpha1.CustomResourceValidation{
+					OpenAPIV3Schema: runtime.RawExtension{
+						Raw: []byte(`{
+							"type": "object",
+							"properties": {
+								"spec": {
+									"type": "object",
+									"properties": {
+										"replicas": {"type": "integer", "minimum": 1},
+										"image": {"type": "string"}
+									},
+									"required": ["image"]
+								},
+								"status": {
+									"type": "object",
+									"properties": {
+										"phase": {"type": "string"}
+									}
+								}
+							},
+							"required": ["spec"]
+						}`),
+					},
+				},
+			},
+			want: want{
+				validation: &extv1.CustomResourceValidation{
+					OpenAPIV3Schema: &extv1.JSONSchemaProps{
+						Type: "object",
+						Properties: map[string]extv1.JSONSchemaProps{
+							"spec": {
+								Type: "object",
+								Properties: map[string]extv1.JSONSchemaProps{
+									"replicas": {
+										Type:    "integer",
+										Minimum: ptr.To(1.0),
+									},
+									"image": {
+										Type: "string",
+									},
+								},
+								Required: []string{"image"},
+							},
+							"status": {
+								Type: "object",
+								Properties: map[string]extv1.JSONSchemaProps{
+									"phase": {
+										Type: "string",
+									},
+								},
+							},
+						},
+						Required: []string{"spec"},
+					},
+				},
+			},
+		},
+		"NilValidation": {
+			reason: "Should return an error for nil validation",
+			args: args{
+				given: nil,
+			},
+			want: want{
+				err: errors.New(errCustomResourceValidationNil),
+			},
+		},
+		"InvalidJSON": {
+			reason: "Should return an error for invalid JSON in schema",
+			args: args{
+				given: &v2alpha1.CustomResourceValidation{
+					OpenAPIV3Schema: runtime.RawExtension{
+						Raw: []byte(invalidJSON),
+					},
+				},
+			},
+			want: want{
+				err: errors.Wrap(errors.New("invalid character 'i' looking for beginning of object key string"), errParseValidation),
+			},
+		},
+		"EmptySchema": {
+			reason: "Should handle empty schema",
+			args: args{
+				given: &v2alpha1.CustomResourceValidation{
+					OpenAPIV3Schema: runtime.RawExtension{
+						Raw: []byte(`{}`),
+					},
+				},
+			},
+			want: want{
+				validation: &extv1.CustomResourceValidation{
+					OpenAPIV3Schema: &extv1.JSONSchemaProps{},
+				},
+			},
+		},
+		"SchemaWithArrayType": {
+			reason: "Should handle schema with array types",
+			args: args{
+				given: &v2alpha1.CustomResourceValidation{
+					OpenAPIV3Schema: runtime.RawExtension{
+						Raw: []byte(`{
+							"type": "object",
+							"properties": {
+								"spec": {
+									"type": "object",
+									"properties": {
+										"items": {
+											"type": "array",
+											"items": {"type": "string"}
+										}
+									}
+								}
+							}
+						}`),
+					},
+				},
+			},
+			want: want{
+				validation: &extv1.CustomResourceValidation{
+					OpenAPIV3Schema: &extv1.JSONSchemaProps{
+						Type: "object",
+						Properties: map[string]extv1.JSONSchemaProps{
+							"spec": {
+								Type: "object",
+								Properties: map[string]extv1.JSONSchemaProps{
+									"items": {
+										Type: "array",
+										Items: &extv1.JSONSchemaPropsOrArray{
+											Schema: &extv1.JSONSchemaProps{
+												Type: "string",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"SchemaWithEnum": {
+			reason: "Should handle schema with enum values",
+			args: args{
+				given: &v2alpha1.CustomResourceValidation{
+					OpenAPIV3Schema: runtime.RawExtension{
+						Raw: []byte(`{
+							"type": "object",
+							"properties": {
+								"spec": {
+									"type": "object",
+									"properties": {
+										"size": {
+											"type": "string",
+											"enum": ["small", "medium", "large"]
+										}
+									}
+								}
+							}
+						}`),
+					},
+				},
+			},
+			want: want{
+				validation: &extv1.CustomResourceValidation{
+					OpenAPIV3Schema: &extv1.JSONSchemaProps{
+						Type: "object",
+						Properties: map[string]extv1.JSONSchemaProps{
+							"spec": {
+								Type: "object",
+								Properties: map[string]extv1.JSONSchemaProps{
+									"size": {
+										Type: "string",
+										Enum: []extv1.JSON{
+											{Raw: []byte(`"small"`)},
+											{Raw: []byte(`"medium"`)},
+											{Raw: []byte(`"large"`)},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got, err := toCustomResourceValidation(tc.args.given)
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\ntoCustomResourceValidation(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.validation, got); diff != "" {
+				t.Errorf("\n%s\ntoCustomResourceValidation(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/controller/ops/cronoperation/reconciler_test.go b/internal/controller/ops/cronoperation/reconciler_test.go
new file mode 100644
index 000000000..c0ad65a2b
--- /dev/null
+++ b/internal/controller/ops/cronoperation/reconciler_test.go
@@ -0,0 +1,697 @@
+/*
+Copyright 2025 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 cronoperation
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
+	"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"
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+)
+
+// EquateApproxDuration returns a cmp.Option that considers two time.Duration values
+// to be equal if they are within the given tolerance of each other. This is useful
+// for testing timing-sensitive operations where minor differences in execution time
+// can cause test failures.
+func EquateApproxDuration(tolerance time.Duration) cmp.Option {
+	return cmp.Comparer(func(x, y time.Duration) bool {
+		diff := x - y
+		if diff < 0 {
+			diff = -diff
+		}
+		return diff <= tolerance
+	})
+}
+
+func TestReconcile(t *testing.T) {
+	now := time.Now()
+	future := now.Add(time.Hour)
+	past := now.Add(-time.Hour)
+
+	type params struct {
+		mgr  manager.Manager
+		opts []ReconcilerOption
+	}
+
+	type want struct {
+		r   reconcile.Result
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		params params
+		want   want
+	}{
+		"NotFound": {
+			reason: "We should return early if the CronOperation was not found.",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
+					},
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"GetError": {
+			reason: "We should return an error if we can't get the CronOperation",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(errors.New("boom")),
+					},
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
+		"Deleted": {
+			reason: "We should return early if the CronOperation was deleted.",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									DeletionTimestamp: ptr.To(metav1.Now()),
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+					},
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"Paused": {
+			reason: "We should return early if the CronOperation is paused.",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Annotations: map[string]string{
+										"crossplane.io/paused": "true",
+									},
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+			},
+			want: want{
+				r: reconcile.Result{},
+			},
+		},
+		"ListOperationsError": {
+			reason: "We should return an error if we can't list Operations",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name: "test-cron",
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule: "0 * * * *",
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList:         test.NewMockListFn(errors.New("boom")),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
+		"InvalidSchedule": {
+			reason: "We should return an error if the cron schedule is invalid",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name: "test-cron",
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule: "invalid-schedule",
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList:         test.NewMockListFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(errors.New("status update failed")),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, _ time.Time) (time.Time, error) {
+						return time.Time{}, errors.New("invalid schedule")
+					})),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
+		"NextScheduledInFuture": {
+			reason: "We should requeue when the next scheduled operation is in the future",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule: "0 * * * *",
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList:         test.NewMockListFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, _ time.Time) (time.Time, error) {
+						return future, nil
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{RequeueAfter: time.Hour},
+			},
+		},
+		"MissedDeadline": {
+			reason: "We should requeue for future when we missed the deadline",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule:                "0 * * * *",
+									StartingDeadlineSeconds: ptr.To(int64(60)), // 1 minute deadline
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList:         test.NewMockListFn(nil),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, last time.Time) (time.Time, error) {
+						if last.Before(past) {
+							// First call: return a time that's past deadline
+							return past.Add(-2 * time.Hour), nil
+						}
+						// Second call: return future time
+						return future, nil
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{RequeueAfter: time.Hour},
+			},
+		},
+		"ConcurrencyPolicyForbid": {
+			reason: "We should requeue for future when concurrency policy forbids and operations are running",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule:          "0 * * * *",
+									ConcurrencyPolicy: ptr.To(v1alpha1.ConcurrencyPolicyForbid),
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error {
+							list := obj.(*v1alpha1.OperationList)
+							// Add a running operation
+							list.Items = []v1alpha1.Operation{
+								{
+									ObjectMeta: metav1.ObjectMeta{
+										Name: "running-op",
+									},
+									Status: v1alpha1.OperationStatus{
+										ConditionedStatus: xpv1.ConditionedStatus{
+											Conditions: []xpv1.Condition{
+												{
+													Type:   v1alpha1.TypeSucceeded,
+													Status: "Unknown",
+													Reason: v1alpha1.ReasonPipelineRunning,
+												},
+											},
+										},
+									},
+								},
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+						MockDelete:       test.NewMockDeleteFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, last time.Time) (time.Time, error) {
+						if last.Before(past) {
+							// First call: return a time that's due now
+							return past.Add(-30 * time.Minute), nil
+						}
+						// Second call: return future time
+						return future, nil
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{RequeueAfter: time.Hour},
+			},
+		},
+		"ConcurrencyPolicyReplaceDeleteError": {
+			reason: "We should return an error if we can't delete running operations for replace policy",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule:          "0 * * * *",
+									ConcurrencyPolicy: ptr.To(v1alpha1.ConcurrencyPolicyReplace),
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error {
+							list := obj.(*v1alpha1.OperationList)
+							// Add a running operation
+							list.Items = []v1alpha1.Operation{
+								{
+									ObjectMeta: metav1.ObjectMeta{
+										Name: "running-op",
+									},
+									Status: v1alpha1.OperationStatus{
+										ConditionedStatus: xpv1.ConditionedStatus{
+											Conditions: []xpv1.Condition{
+												{
+													Type:   v1alpha1.TypeSucceeded,
+													Status: "Unknown",
+													Reason: v1alpha1.ReasonPipelineRunning,
+												},
+											},
+										},
+									},
+								},
+							}
+							return nil
+						}),
+						MockDelete:       test.NewMockDeleteFn(errors.New("boom")),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, _ time.Time) (time.Time, error) {
+						// Return a time that's due now
+						return past.Add(-30 * time.Minute), nil
+					})),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
+		"CreateOperationError": {
+			reason: "We should return an error if we can't create the scheduled Operation",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule: "0 * * * *",
+									OperationTemplate: v1alpha1.OperationTemplate{
+										Spec: v1alpha1.OperationSpec{
+											Mode: v1alpha1.OperationModePipeline,
+											Pipeline: []v1alpha1.PipelineStep{
+												{
+													Step: "test-step",
+													FunctionRef: v1alpha1.FunctionReference{
+														Name: "test-function",
+													},
+												},
+											},
+										},
+									},
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList:         test.NewMockListFn(nil),
+						MockCreate:       test.NewMockCreateFn(errors.New("boom")),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, _ time.Time) (time.Time, error) {
+						// Return a time that's due now
+						return past.Add(-30 * time.Minute), nil
+					})),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
+		"Success": {
+			reason: "We should successfully create an operation and update status",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									UID:               types.UID("test-uid"),
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule: "0 * * * *",
+									OperationTemplate: v1alpha1.OperationTemplate{
+										Spec: v1alpha1.OperationSpec{
+											Mode: v1alpha1.OperationModePipeline,
+											Pipeline: []v1alpha1.PipelineStep{
+												{
+													Step: "test-step",
+													FunctionRef: v1alpha1.FunctionReference{
+														Name: "test-function",
+													},
+												},
+											},
+										},
+									},
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList: test.NewMockListFn(nil),
+						MockCreate: test.NewMockCreateFn(nil, func(obj client.Object) error {
+							// Verify the created operation has the right properties
+							op := obj.(*v1alpha1.Operation)
+							if op.Name != "test-cron--2147483648" { // Unix timestamp will be negative due to past time
+								t.Errorf("Expected operation name to contain cron name, got: %s", op.Name)
+							}
+							if op.Labels[v1alpha1.LabelCronOperationName] != "test-cron" {
+								t.Errorf("Expected operation to have cron operation label")
+							}
+							if len(op.OwnerReferences) != 1 || op.OwnerReferences[0].Name != "test-cron" {
+								t.Errorf("Expected operation to have owner reference to cron operation")
+							}
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, last time.Time) (time.Time, error) {
+						if last.Before(past) {
+							// First call: return a time that's due now
+							return past.Add(-30 * time.Minute), nil
+						}
+						// Second call: return future time
+						return future, nil
+					})),
+				},
+			},
+			want: want{
+				r: reconcile.Result{RequeueAfter: time.Hour},
+			},
+		},
+		"GarbageCollectionError": {
+			reason: "We should return an error if we can't garbage collect old operations",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							co := &v1alpha1.CronOperation{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:              "test-cron",
+									CreationTimestamp: metav1.Time{Time: past},
+								},
+								Spec: v1alpha1.CronOperationSpec{
+									Schedule:               "0 * * * *",
+									SuccessfulHistoryLimit: ptr.To(int32(1)),
+									FailedHistoryLimit:     ptr.To(int32(1)),
+								},
+							}
+							co.DeepCopyInto(obj.(*v1alpha1.CronOperation))
+							return nil
+						}),
+						MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error {
+							list := obj.(*v1alpha1.OperationList)
+							// Add old operations that should be garbage collected
+							list.Items = []v1alpha1.Operation{
+								{
+									ObjectMeta: metav1.ObjectMeta{
+										Name:              "old-op",
+										CreationTimestamp: metav1.Time{Time: past.Add(-2 * time.Hour)},
+									},
+									Status: v1alpha1.OperationStatus{
+										ConditionedStatus: xpv1.ConditionedStatus{
+											Conditions: []xpv1.Condition{
+												{
+													Type:   v1alpha1.TypeSucceeded,
+													Status: "True",
+													Reason: v1alpha1.ReasonPipelineSuccess,
+												},
+											},
+										},
+									},
+								},
+								{
+									ObjectMeta: metav1.ObjectMeta{
+										Name:              "newer-op",
+										CreationTimestamp: metav1.Time{Time: past.Add(-1 * time.Hour)},
+									},
+									Status: v1alpha1.OperationStatus{
+										ConditionedStatus: xpv1.ConditionedStatus{
+											Conditions: []xpv1.Condition{
+												{
+													Type:   v1alpha1.TypeSucceeded,
+													Status: "True",
+													Reason: v1alpha1.ReasonPipelineSuccess,
+												},
+											},
+										},
+									},
+								},
+							}
+							return nil
+						}),
+						MockDelete:       test.NewMockDeleteFn(errors.New("boom")),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithScheduler(SchedulerFn(func(_ string, _ time.Time) (time.Time, error) {
+						return future, nil
+					})),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			r := NewReconciler(tc.params.mgr, tc.params.opts...)
+
+			got, err := r.Reconcile(context.Background(), reconcile.Request{
+				NamespacedName: types.NamespacedName{Name: "test-cron"},
+			})
+
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.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, EquateApproxDuration(1*time.Second)); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want result, +got result:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestNewOperation(t *testing.T) {
+	scheduled := time.Unix(1609459200, 0) // 2021-01-01 00:00:00 UTC
+
+	type args struct {
+		co        *v1alpha1.CronOperation
+		scheduled time.Time
+	}
+	type want struct {
+		op *v1alpha1.Operation
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"Success": {
+			reason: "Should create operation with correct metadata and owner reference",
+			args: args{
+				co: &v1alpha1.CronOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-cron",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.CronOperationSpec{
+						OperationTemplate: v1alpha1.OperationTemplate{
+							ObjectMeta: metav1.ObjectMeta{
+								Labels: map[string]string{
+									"template": "label",
+								},
+							},
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				scheduled: scheduled,
+			},
+			want: want{
+				op: &v1alpha1.Operation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-cron-1609459200",
+						Labels: map[string]string{
+							"template":                      "label",
+							v1alpha1.LabelCronOperationName: "test-cron",
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion:         "ops.crossplane.io/v1alpha1",
+								Kind:               "CronOperation",
+								Name:               "test-cron",
+								UID:                types.UID("test-uid"),
+								Controller:         ptr.To(true),
+								BlockOwnerDeletion: ptr.To(true),
+							},
+						},
+					},
+					Spec: v1alpha1.OperationSpec{
+						Mode: v1alpha1.OperationModePipeline,
+						Pipeline: []v1alpha1.PipelineStep{
+							{
+								Step: "test-step",
+								FunctionRef: v1alpha1.FunctionReference{
+									Name: "test-function",
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := NewOperation(tc.args.co, tc.args.scheduled)
+			if diff := cmp.Diff(tc.want.op, got); diff != "" {
+				t.Errorf("\n%s\nNewOperation(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/controller/ops/operation/reconciler_test.go b/internal/controller/ops/operation/reconciler_test.go
index 4026b4ba5..eb818d764 100644
--- a/internal/controller/ops/operation/reconciler_test.go
+++ b/internal/controller/ops/operation/reconciler_test.go
@@ -418,6 +418,55 @@ func TestReconcile(t *testing.T) {
 				err: cmpopts.AnyError,
 			},
 		},
+		"BootstrapRequirementsFetchError": {
+			reason: "We should return an error if we can't fetch bootstrap requirements",
+			params: params{
+				mgr: &fake.Manager{
+					Client: &test.MockClient{
+						MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
+							op := &v1alpha1.Operation{
+								Spec: v1alpha1.OperationSpec{
+									Pipeline: []v1alpha1.PipelineStep{
+										{
+											Step: "requires-resources",
+											FunctionRef: v1alpha1.FunctionReference{
+												Name: "function-cool",
+											},
+											Requirements: &v1alpha1.FunctionRequirements{
+												RequiredResources: []v1alpha1.RequiredResourceSelector{
+													{
+														RequirementName: "test-resources",
+														APIVersion:      "v1",
+														Kind:            "ConfigMap",
+														Name:            ptr.To("missing-configmap"),
+													},
+												},
+											},
+										},
+									},
+								},
+							}
+							op.DeepCopyInto(obj.(*v1alpha1.Operation))
+
+							return nil
+						}),
+						MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+					},
+				},
+				opts: []ReconcilerOption{
+					WithCapabilityChecker(xfn.CapabilityCheckerFn(func(_ context.Context, _ []string, _ ...string) error {
+						return nil
+					})),
+					WithRequiredResourcesFetcher(xfn.RequiredResourcesFetcherFn(func(_ context.Context, _ *fnv1.ResourceSelector) (*fnv1.Resources, error) {
+						return nil, errors.New("boom")
+					})),
+				},
+			},
+			want: want{
+				r:   reconcile.Result{},
+				err: cmpopts.AnyError,
+			},
+		},
 		"Success": {
 			reason: "We shouldn't return an error if we successfully run the Operation",
 			params: params{
@@ -626,6 +675,169 @@ func TestAddPipelineStepOutput(t *testing.T) {
 	}
 }
 
+func TestToProtobufResourceSelector(t *testing.T) {
+	type args struct {
+		selector v1alpha1.RequiredResourceSelector
+	}
+
+	type want struct {
+		selector *fnv1.ResourceSelector
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"BasicSelector": {
+			reason: "Should convert basic API version and kind",
+			args: args{
+				selector: v1alpha1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "Pod",
+				},
+			},
+			want: want{
+				selector: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "Pod",
+				},
+			},
+		},
+		"SelectorWithName": {
+			reason: "Should convert selector with specific name",
+			args: args{
+				selector: v1alpha1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "Secret",
+					Name:            ptr.To("test-secret"),
+				},
+			},
+			want: want{
+				selector: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "Secret",
+					Match: &fnv1.ResourceSelector_MatchName{
+						MatchName: "test-secret",
+					},
+				},
+			},
+		},
+		"SelectorWithLabels": {
+			reason: "Should convert selector with match labels",
+			args: args{
+				selector: v1alpha1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "Pod",
+					MatchLabels: map[string]string{
+						"app": "test",
+						"env": "prod",
+					},
+				},
+			},
+			want: want{
+				selector: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "Pod",
+					Match: &fnv1.ResourceSelector_MatchLabels{
+						MatchLabels: &fnv1.MatchLabels{
+							Labels: map[string]string{
+								"app": "test",
+								"env": "prod",
+							},
+						},
+					},
+				},
+			},
+		},
+		"SelectorWithNamespace": {
+			reason: "Should convert namespaced selector",
+			args: args{
+				selector: v1alpha1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "ConfigMap",
+					Namespace:       ptr.To("default"),
+				},
+			},
+			want: want{
+				selector: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "ConfigMap",
+					Namespace:  ptr.To("default"),
+				},
+			},
+		},
+		"SelectorWithNameAndNamespace": {
+			reason: "Should convert selector with both name and namespace",
+			args: args{
+				selector: v1alpha1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "v1",
+					Kind:            "Secret",
+					Name:            ptr.To("my-secret"),
+					Namespace:       ptr.To("kube-system"),
+				},
+			},
+			want: want{
+				selector: &fnv1.ResourceSelector{
+					ApiVersion: "v1",
+					Kind:       "Secret",
+					Namespace:  ptr.To("kube-system"),
+					Match: &fnv1.ResourceSelector_MatchName{
+						MatchName: "my-secret",
+					},
+				},
+			},
+		},
+		"SelectorWithLabelsAndNamespace": {
+			reason: "Should convert selector with labels and namespace",
+			args: args{
+				selector: v1alpha1.RequiredResourceSelector{
+					RequirementName: "test-req",
+					APIVersion:      "apps/v1",
+					Kind:            "Deployment",
+					MatchLabels: map[string]string{
+						"tier": "frontend",
+					},
+					Namespace: ptr.To("production"),
+				},
+			},
+			want: want{
+				selector: &fnv1.ResourceSelector{
+					ApiVersion: "apps/v1",
+					Kind:       "Deployment",
+					Namespace:  ptr.To("production"),
+					Match: &fnv1.ResourceSelector_MatchLabels{
+						MatchLabels: &fnv1.MatchLabels{
+							Labels: map[string]string{
+								"tier": "frontend",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := ToProtobufResourceSelector(tc.args.selector)
+			if diff := cmp.Diff(tc.want.selector, got, cmpopts.IgnoreUnexported(
+				fnv1.ResourceSelector{},
+				fnv1.ResourceSelector_MatchName{},
+				fnv1.ResourceSelector_MatchLabels{},
+				fnv1.MatchLabels{},
+			)); diff != "" {
+				t.Errorf("\n%s\nToProtobufResourceSelector(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
 func TestAddResourceRef(t *testing.T) {
 	type args struct {
 		refs []v1alpha1.AppliedResourceRef
diff --git a/internal/controller/ops/watched/reconciler_test.go b/internal/controller/ops/watched/reconciler_test.go
new file mode 100644
index 000000000..c48e51513
--- /dev/null
+++ b/internal/controller/ops/watched/reconciler_test.go
@@ -0,0 +1,1036 @@
+/*
+Copyright 2025 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 watched
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+)
+
+var errBoom = errors.New("boom")
+
+func TestReconcile(t *testing.T) {
+	type params struct {
+		client  client.Client
+		options []ReconcilerOption
+		wo      *v1alpha1.WatchOperation
+	}
+	type args struct {
+		ctx context.Context
+		req reconcile.Request
+	}
+	type want struct {
+		result reconcile.Result
+		err    error
+	}
+
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"CreateOperationForDeletedResource": {
+			reason: "Should create Operation with synthetic resource when watched resource is deleted",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if _, ok := obj.(*unstructured.Unstructured); ok {
+							// Return not found for the watched resource (deletion scenario)
+							return kerrors.NewNotFound(schema.GroupResource{}, "")
+						}
+						if wo, ok := obj.(*v1alpha1.WatchOperation); ok {
+							// Return WatchOperation
+							wo.SetName("test-watch")
+							wo.SetUID("test-uid")
+							wo.Spec.OperationTemplate = v1alpha1.OperationTemplate{
+								Spec: v1alpha1.OperationSpec{
+									Mode: v1alpha1.OperationModePipeline,
+									Pipeline: []v1alpha1.PipelineStep{
+										{
+											Step: "test-step",
+											FunctionRef: v1alpha1.FunctionReference{
+												Name: "test-function",
+											},
+										},
+									},
+								},
+							}
+							return nil
+						}
+						return errBoom
+					},
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						if ol, ok := list.(*v1alpha1.OperationList); ok {
+							// Return empty list
+							ol.Items = []v1alpha1.Operation{}
+							return nil
+						}
+						return errBoom
+					},
+					MockCreate: test.NewMockCreateFn(nil),
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+						OperationTemplate: v1alpha1.OperationTemplate{
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    nil,
+			},
+		},
+		"GetError": {
+			reason: "Should return an error if getting watched resource fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"WatchOperationNotFound": {
+			reason: "Should return early if WatchOperation is not found",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if _, ok := obj.(*unstructured.Unstructured); ok {
+							// Return watched resource
+							u := obj.(*unstructured.Unstructured)
+							u.SetUID("test-uid")
+							u.SetResourceVersion("123")
+							return nil
+						}
+						// Return not found for WatchOperation
+						return kerrors.NewNotFound(schema.GroupResource{}, "")
+					},
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    nil,
+			},
+		},
+		"Paused": {
+			reason: "Should return early if WatchOperation is paused",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if u, ok := obj.(*unstructured.Unstructured); ok {
+							// Return watched resource
+							u.SetUID("test-uid")
+							u.SetResourceVersion("123")
+							return nil
+						}
+						if wo, ok := obj.(*v1alpha1.WatchOperation); ok {
+							// Return paused WatchOperation
+							wo.SetName("test-watch")
+							wo.SetUID("test-uid")
+							wo.SetAnnotations(map[string]string{
+								"crossplane.io/paused": "true",
+							})
+							return nil
+						}
+						return errBoom
+					},
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+						Annotations: map[string]string{
+							"crossplane.io/paused": "true",
+						},
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{Requeue: false},
+				err:    nil,
+			},
+		},
+		"Deleted": {
+			reason: "Should return early if WatchOperation is being deleted",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if u, ok := obj.(*unstructured.Unstructured); ok {
+							// Return watched resource
+							u.SetUID("test-uid")
+							u.SetResourceVersion("123")
+							return nil
+						}
+						if wo, ok := obj.(*v1alpha1.WatchOperation); ok {
+							// Return deleted WatchOperation
+							wo.SetName("test-watch")
+							wo.SetUID("test-uid")
+							now := metav1.Now()
+							wo.SetDeletionTimestamp(&now)
+							return nil
+						}
+						return errBoom
+					},
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{Requeue: false},
+				err:    nil,
+			},
+		},
+		"ListOperationsError": {
+			reason: "Should return an error if listing Operations fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if u, ok := obj.(*unstructured.Unstructured); ok {
+							// Return watched resource
+							u.SetUID("test-uid")
+							u.SetResourceVersion("123")
+							return nil
+						}
+						if wo, ok := obj.(*v1alpha1.WatchOperation); ok {
+							// Return WatchOperation
+							wo.SetName("test-watch")
+							wo.SetUID("test-uid")
+							return nil
+						}
+						return errBoom
+					},
+					MockList: test.NewMockListFn(errBoom),
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"CreateOperation": {
+			reason: "Should create an Operation when watched resource changes",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if u, ok := obj.(*unstructured.Unstructured); ok {
+							// Return watched resource
+							u.SetUID("test-uid")
+							u.SetResourceVersion("123")
+							return nil
+						}
+						if wo, ok := obj.(*v1alpha1.WatchOperation); ok {
+							// Return WatchOperation
+							wo.SetName("test-watch")
+							wo.SetUID("test-uid")
+							wo.Spec.OperationTemplate = v1alpha1.OperationTemplate{
+								Spec: v1alpha1.OperationSpec{
+									Mode: v1alpha1.OperationModePipeline,
+									Pipeline: []v1alpha1.PipelineStep{
+										{
+											Step: "test-step",
+											FunctionRef: v1alpha1.FunctionReference{
+												Name: "test-function",
+											},
+										},
+									},
+								},
+							}
+							return nil
+						}
+						return errBoom
+					},
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						if ol, ok := list.(*v1alpha1.OperationList); ok {
+							// Return empty list
+							ol.Items = []v1alpha1.Operation{}
+							return nil
+						}
+						return errBoom
+					},
+					MockCreate: test.NewMockCreateFn(nil),
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+						OperationTemplate: v1alpha1.OperationTemplate{
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    nil,
+			},
+		},
+		"OperationAlreadyExists": {
+			reason: "Should not create duplicate Operation for same resource version",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						if u, ok := obj.(*unstructured.Unstructured); ok {
+							// Return watched resource
+							u.SetUID("test-uid")
+							u.SetResourceVersion("123")
+							return nil
+						}
+						if wo, ok := obj.(*v1alpha1.WatchOperation); ok {
+							// Return WatchOperation
+							wo.SetName("test-watch")
+							wo.SetUID("test-uid")
+							return nil
+						}
+						return errBoom
+					},
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						if ol, ok := list.(*v1alpha1.OperationList); ok {
+							// Return existing operation with same name
+							watched := &unstructured.Unstructured{
+								Object: map[string]any{
+									"metadata": map[string]any{
+										"uid":             "test-uid",
+										"resourceVersion": "123",
+									},
+								},
+							}
+							watched.SetGroupVersionKind(schema.GroupVersionKind{
+								Group:   "",
+								Version: "v1",
+								Kind:    "Pod",
+							})
+							expectedName := OperationName(&v1alpha1.WatchOperation{
+								ObjectMeta: metav1.ObjectMeta{Name: "test-watch"},
+							}, watched)
+							ol.Items = []v1alpha1.Operation{
+								{
+									ObjectMeta: metav1.ObjectMeta{
+										Name: expectedName,
+									},
+								},
+							}
+							return nil
+						}
+						return errBoom
+					},
+				},
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						Watch: v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						},
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: "default",
+						Name:      "test-pod",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    nil,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			r := NewReconciler(tc.params.client, tc.params.wo, tc.params.options...)
+			got, err := r.Reconcile(tc.args.ctx, tc.args.req)
+
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.result, got); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want result, +got result:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestOperationName(t *testing.T) {
+	type args struct {
+		wo      *v1alpha1.WatchOperation
+		watched *unstructured.Unstructured
+	}
+	type want struct {
+		name string
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"Success": {
+			reason: "Should create deterministic name from WatchOperation name and watched resource UID/version",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Pod",
+						"metadata": map[string]any{
+							"name":            "test-pod",
+							"namespace":       "default",
+							"uid":             "test-uid",
+							"resourceVersion": "123",
+						},
+					},
+				},
+			},
+			want: want{
+				name: "test-watch-2ae9bd3",
+			},
+		},
+		"DifferentResourceVersion": {
+			reason: "Should create different name for different resource version",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Pod",
+						"metadata": map[string]any{
+							"name":            "test-pod",
+							"namespace":       "default",
+							"uid":             "test-uid",
+							"resourceVersion": "124",
+						},
+					},
+				},
+			},
+			want: want{
+				name: "test-watch-a1051fc",
+			},
+		},
+		"SyntheticDeletedResource": {
+			reason: "Should create unique names for synthetic deleted resources with deletion timestamp",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Pod",
+						"metadata": map[string]any{
+							"name":              "test-pod",
+							"namespace":         "default",
+							"resourceVersion":   v1alpha1.SyntheticResourceVersionDeleted,
+							"deletionTimestamp": "2023-01-01T00:00:00Z", // Simulates the deletion timestamp
+						},
+					},
+				},
+			},
+			want: want{
+				name: "test-watch-a95edb5", // Hash includes deletion timestamp for uniqueness
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := OperationName(tc.args.wo, tc.args.watched)
+			if diff := cmp.Diff(tc.want.name, got); diff != "" {
+				t.Errorf("\n%s\nOperationName(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestNewOperation(t *testing.T) {
+	type args struct {
+		wo      *v1alpha1.WatchOperation
+		watched *unstructured.Unstructured
+		name    string
+	}
+	type want struct {
+		op *v1alpha1.Operation
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"WatchedResourceInjected": {
+			reason: "Should inject watched resource into all pipeline steps",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						OperationTemplate: v1alpha1.OperationTemplate{
+							ObjectMeta: metav1.ObjectMeta{
+								Labels: map[string]string{
+									"template": "label",
+								},
+							},
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Pod",
+						"metadata": map[string]any{
+							"name":      "test-pod",
+							"namespace": "default",
+						},
+					},
+				},
+				name: "test-watch-abcdef1",
+			},
+			want: want{
+				op: &v1alpha1.Operation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch-abcdef1",
+						Labels: map[string]string{
+							"template":                       "label",
+							v1alpha1.LabelWatchOperationName: "test-watch",
+						},
+						Annotations: map[string]string{
+							v1alpha1.AnnotationWatchedResourceAPIVersion:      "v1",
+							v1alpha1.AnnotationWatchedResourceKind:            "Pod",
+							v1alpha1.AnnotationWatchedResourceName:            "test-pod",
+							v1alpha1.AnnotationWatchedResourceNamespace:       "default",
+							v1alpha1.AnnotationWatchedResourceResourceVersion: "",
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion:         "ops.crossplane.io/v1alpha1",
+								Kind:               "WatchOperation",
+								Name:               "test-watch",
+								UID:                types.UID("test-uid"),
+								Controller:         ptr.To(true),
+								BlockOwnerDeletion: ptr.To(true),
+							},
+						},
+					},
+					Spec: v1alpha1.OperationSpec{
+						Mode: v1alpha1.OperationModePipeline,
+						Pipeline: []v1alpha1.PipelineStep{
+							{
+								Step: "test-step",
+								FunctionRef: v1alpha1.FunctionReference{
+									Name: "test-function",
+								},
+								Requirements: &v1alpha1.FunctionRequirements{
+									RequiredResources: []v1alpha1.RequiredResourceSelector{
+										{
+											RequirementName: v1alpha1.RequirementNameWatchedResource,
+											APIVersion:      "v1",
+											Kind:            "Pod",
+											Namespace:       ptr.To("default"),
+											Name:            ptr.To("test-pod"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"ClusterScopedResource": {
+			reason: "Should inject cluster-scoped watched resource without namespace",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						OperationTemplate: v1alpha1.OperationTemplate{
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Node",
+						"metadata": map[string]any{
+							"name": "test-node",
+						},
+					},
+				},
+				name: "test-watch-abcdef1",
+			},
+			want: want{
+				op: &v1alpha1.Operation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch-abcdef1",
+						Labels: map[string]string{
+							v1alpha1.LabelWatchOperationName: "test-watch",
+						},
+						Annotations: map[string]string{
+							v1alpha1.AnnotationWatchedResourceAPIVersion:      "v1",
+							v1alpha1.AnnotationWatchedResourceKind:            "Node",
+							v1alpha1.AnnotationWatchedResourceName:            "test-node",
+							v1alpha1.AnnotationWatchedResourceResourceVersion: "",
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion:         "ops.crossplane.io/v1alpha1",
+								Kind:               "WatchOperation",
+								Name:               "test-watch",
+								UID:                types.UID("test-uid"),
+								Controller:         ptr.To(true),
+								BlockOwnerDeletion: ptr.To(true),
+							},
+						},
+					},
+					Spec: v1alpha1.OperationSpec{
+						Mode: v1alpha1.OperationModePipeline,
+						Pipeline: []v1alpha1.PipelineStep{
+							{
+								Step: "test-step",
+								FunctionRef: v1alpha1.FunctionReference{
+									Name: "test-function",
+								},
+								Requirements: &v1alpha1.FunctionRequirements{
+									RequiredResources: []v1alpha1.RequiredResourceSelector{
+										{
+											RequirementName: v1alpha1.RequirementNameWatchedResource,
+											APIVersion:      "v1",
+											Kind:            "Node",
+											Name:            ptr.To("test-node"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"PreservesExistingRequirements": {
+			reason: "Should preserve existing requirements while adding watched resource",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						OperationTemplate: v1alpha1.OperationTemplate{
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+										Requirements: &v1alpha1.FunctionRequirements{
+											RequiredResources: []v1alpha1.RequiredResourceSelector{
+												{
+													RequirementName: "existing-requirement",
+													APIVersion:      "v1",
+													Kind:            "Secret",
+													Name:            ptr.To("existing-secret"),
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Pod",
+						"metadata": map[string]any{
+							"name":      "test-pod",
+							"namespace": "default",
+						},
+					},
+				},
+				name: "test-watch-abcdef1",
+			},
+			want: want{
+				op: &v1alpha1.Operation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch-abcdef1",
+						Labels: map[string]string{
+							v1alpha1.LabelWatchOperationName: "test-watch",
+						},
+						Annotations: map[string]string{
+							v1alpha1.AnnotationWatchedResourceAPIVersion:      "v1",
+							v1alpha1.AnnotationWatchedResourceKind:            "Pod",
+							v1alpha1.AnnotationWatchedResourceName:            "test-pod",
+							v1alpha1.AnnotationWatchedResourceNamespace:       "default",
+							v1alpha1.AnnotationWatchedResourceResourceVersion: "",
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion:         "ops.crossplane.io/v1alpha1",
+								Kind:               "WatchOperation",
+								Name:               "test-watch",
+								UID:                types.UID("test-uid"),
+								Controller:         ptr.To(true),
+								BlockOwnerDeletion: ptr.To(true),
+							},
+						},
+					},
+					Spec: v1alpha1.OperationSpec{
+						Mode: v1alpha1.OperationModePipeline,
+						Pipeline: []v1alpha1.PipelineStep{
+							{
+								Step: "test-step",
+								FunctionRef: v1alpha1.FunctionReference{
+									Name: "test-function",
+								},
+								Requirements: &v1alpha1.FunctionRequirements{
+									RequiredResources: []v1alpha1.RequiredResourceSelector{
+										{
+											RequirementName: "existing-requirement",
+											APIVersion:      "v1",
+											Kind:            "Secret",
+											Name:            ptr.To("existing-secret"),
+										},
+										{
+											RequirementName: v1alpha1.RequirementNameWatchedResource,
+											APIVersion:      "v1",
+											Kind:            "Pod",
+											Namespace:       ptr.To("default"),
+											Name:            ptr.To("test-pod"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"SyntheticDeletedResource": {
+			reason: "Should create Operation with synthetic resource version for deleted resource",
+			args: args{
+				wo: &v1alpha1.WatchOperation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch",
+						UID:  types.UID("test-uid"),
+					},
+					Spec: v1alpha1.WatchOperationSpec{
+						OperationTemplate: v1alpha1.OperationTemplate{
+							Spec: v1alpha1.OperationSpec{
+								Mode: v1alpha1.OperationModePipeline,
+								Pipeline: []v1alpha1.PipelineStep{
+									{
+										Step: "test-step",
+										FunctionRef: v1alpha1.FunctionReference{
+											Name: "test-function",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				watched: &unstructured.Unstructured{
+					Object: map[string]any{
+						"apiVersion": "v1",
+						"kind":       "Pod",
+						"metadata": map[string]any{
+							"name":              "test-pod",
+							"namespace":         "default",
+							"resourceVersion":   v1alpha1.SyntheticResourceVersionDeleted,
+							"deletionTimestamp": "2023-01-01T00:00:00Z", // Simulates the deletion timestamp
+						},
+					},
+				},
+				name: "test-watch-abcdef1",
+			},
+			want: want{
+				op: &v1alpha1.Operation{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-watch-abcdef1",
+						Labels: map[string]string{
+							v1alpha1.LabelWatchOperationName: "test-watch",
+						},
+						Annotations: map[string]string{
+							v1alpha1.AnnotationWatchedResourceAPIVersion:      "v1",
+							v1alpha1.AnnotationWatchedResourceKind:            "Pod",
+							v1alpha1.AnnotationWatchedResourceName:            "test-pod",
+							v1alpha1.AnnotationWatchedResourceNamespace:       "default",
+							v1alpha1.AnnotationWatchedResourceResourceVersion: v1alpha1.SyntheticResourceVersionDeleted,
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion:         "ops.crossplane.io/v1alpha1",
+								Kind:               "WatchOperation",
+								Name:               "test-watch",
+								UID:                types.UID("test-uid"),
+								Controller:         ptr.To(true),
+								BlockOwnerDeletion: ptr.To(true),
+							},
+						},
+					},
+					Spec: v1alpha1.OperationSpec{
+						Mode: v1alpha1.OperationModePipeline,
+						Pipeline: []v1alpha1.PipelineStep{
+							{
+								Step: "test-step",
+								FunctionRef: v1alpha1.FunctionReference{
+									Name: "test-function",
+								},
+								Requirements: &v1alpha1.FunctionRequirements{
+									RequiredResources: []v1alpha1.RequiredResourceSelector{
+										{
+											RequirementName: v1alpha1.RequirementNameWatchedResource,
+											APIVersion:      "v1",
+											Kind:            "Pod",
+											Namespace:       ptr.To("default"),
+											Name:            ptr.To("test-pod"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := NewOperation(tc.args.wo, tc.args.watched, tc.args.name)
+			if diff := cmp.Diff(tc.want.op, got); diff != "" {
+				t.Errorf("\n%s\nNewOperation(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/controller/ops/watchoperation/reconciler_test.go b/internal/controller/ops/watchoperation/reconciler_test.go
new file mode 100644
index 000000000..94fef3fc4
--- /dev/null
+++ b/internal/controller/ops/watchoperation/reconciler_test.go
@@ -0,0 +1,539 @@
+/*
+Copyright 2025 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 watchoperation
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	"github.com/crossplane/crossplane/internal/engine"
+)
+
+var errBoom = errors.New("boom")
+
+// MockEngine implements the ControllerEngine interface for testing.
+type MockEngine struct {
+	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(ctx context.Context, name string, ws ...engine.Watch) error
+	MockStopWatches  func(ctx context.Context, name string, ws ...engine.WatchID) (int, error)
+	MockGetCached    func() client.Client
+	MockGetUncached  func() client.Client
+}
+
+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) IsRunning(name string) bool {
+	return m.MockIsRunning(name)
+}
+
+func (m *MockEngine) GetWatches(name string) ([]engine.WatchID, error) {
+	return m.MockGetWatches(name)
+}
+
+func (m *MockEngine) StartWatches(ctx context.Context, name string, ws ...engine.Watch) error {
+	return m.MockStartWatches(ctx, name, ws...)
+}
+
+func (m *MockEngine) StopWatches(ctx context.Context, name string, ws ...engine.WatchID) (int, error) {
+	return m.MockStopWatches(ctx, name, ws...)
+}
+
+func (m *MockEngine) GetCached() client.Client {
+	return m.MockGetCached()
+}
+
+func (m *MockEngine) GetUncached() client.Client {
+	return m.MockGetUncached()
+}
+
+func (m *MockEngine) GetFieldIndexer() client.FieldIndexer {
+	return nil
+}
+
+func TestReconcile(t *testing.T) {
+	type params struct {
+		client client.Client
+		engine ControllerEngine
+	}
+	type args struct {
+		ctx context.Context
+		req reconcile.Request
+	}
+	type want struct {
+		result reconcile.Result
+		err    error
+	}
+
+	cases := map[string]struct {
+		reason string
+		params params
+		args   args
+		want   want
+	}{
+		"NotFound": {
+			reason: "Should return early if WatchOperation is not found",
+			params: params{
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
+				},
+				engine: &MockEngine{},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    nil,
+			},
+		},
+		"GetError": {
+			reason: "Should return an error if getting WatchOperation fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
+				},
+				engine: &MockEngine{},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"DeletedStopError": {
+			reason: "Should return an error if stopping watched controller fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						now := metav1.Now()
+						wo.SetDeletionTimestamp(&now)
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{
+					MockStop: func(_ context.Context, _ string) error {
+						return errBoom
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"DeletedRemoveFinalizerError": {
+			reason: "Should return an error if removing finalizer fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						now := metav1.Now()
+						wo.SetDeletionTimestamp(&now)
+						wo.SetFinalizers([]string{finalizer})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockUpdate:       test.NewMockUpdateFn(errBoom),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{
+					MockStop: func(_ context.Context, _ string) error {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"DeletedSuccess": {
+			reason: "Should successfully delete WatchOperation and stop controller",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						now := metav1.Now()
+						wo.SetDeletionTimestamp(&now)
+						wo.SetFinalizers([]string{finalizer})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockUpdate: test.NewMockUpdateFn(nil),
+				},
+				engine: &MockEngine{
+					MockStop: func(_ context.Context, _ string) error {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{Requeue: false},
+				err:    nil,
+			},
+		},
+		"AddFinalizerError": {
+			reason: "Should return an error if adding finalizer fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockUpdate:       test.NewMockUpdateFn(errBoom),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"Paused": {
+			reason: "Should return early if WatchOperation is paused",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						wo.SetFinalizers([]string{finalizer})
+						wo.SetAnnotations(map[string]string{
+							"crossplane.io/paused": "true",
+						})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{Requeue: false},
+				err:    nil,
+			},
+		},
+		"ListOperationsError": {
+			reason: "Should return an error if listing Operations fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						wo.SetFinalizers([]string{finalizer})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockList:         test.NewMockListFn(errBoom),
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"StartControllerError": {
+			reason: "Should return an error if starting watched controller fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						wo.SetFinalizers([]string{finalizer})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						if ol, ok := list.(*v1alpha1.OperationList); ok {
+							ol.Items = []v1alpha1.Operation{}
+							return nil
+						}
+						if ul, ok := list.(*unstructured.UnstructuredList); ok {
+							ul.Items = []unstructured.Unstructured{}
+							return nil
+						}
+						return errBoom
+					},
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{
+					MockGetCached: func() client.Client {
+						return &test.MockClient{}
+					},
+					MockStart: func(_ string, _ ...engine.ControllerOption) error {
+						return errBoom
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"StartWatchesError": {
+			reason: "Should return an error if starting watches fails",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						wo.SetFinalizers([]string{finalizer})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						if ol, ok := list.(*v1alpha1.OperationList); ok {
+							ol.Items = []v1alpha1.Operation{}
+							return nil
+						}
+						if ul, ok := list.(*unstructured.UnstructuredList); ok {
+							ul.Items = []unstructured.Unstructured{}
+							return nil
+						}
+						return errBoom
+					},
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{
+					MockGetCached: func() client.Client {
+						return &test.MockClient{}
+					},
+					MockStart: func(_ string, _ ...engine.ControllerOption) error {
+						return nil
+					},
+					MockStartWatches: func(_ context.Context, _ string, _ ...engine.Watch) error {
+						return errBoom
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{},
+				err:    cmpopts.AnyError,
+			},
+		},
+		"Success": {
+			reason: "Should successfully reconcile WatchOperation and start controller",
+			params: params{
+				client: &test.MockClient{
+					MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
+						wo := obj.(*v1alpha1.WatchOperation)
+						wo.SetName("test-watch")
+						wo.SetUID("test-uid")
+						wo.SetFinalizers([]string{finalizer})
+						wo.Spec.Watch = v1alpha1.WatchSpec{
+							APIVersion: "v1",
+							Kind:       "Pod",
+						}
+						return nil
+					},
+					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
+						if ol, ok := list.(*v1alpha1.OperationList); ok {
+							ol.Items = []v1alpha1.Operation{}
+							return nil
+						}
+						if ul, ok := list.(*unstructured.UnstructuredList); ok {
+							ul.Items = []unstructured.Unstructured{}
+							return nil
+						}
+						return errBoom
+					},
+					MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil),
+				},
+				engine: &MockEngine{
+					MockGetCached: func() client.Client {
+						return &test.MockClient{}
+					},
+					MockStart: func(_ string, _ ...engine.ControllerOption) error {
+						return nil
+					},
+					MockStartWatches: func(_ context.Context, _ string, _ ...engine.Watch) error {
+						return nil
+					},
+				},
+			},
+			args: args{
+				ctx: context.Background(),
+				req: reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name: "test-watch",
+					},
+				},
+			},
+			want: want{
+				result: reconcile.Result{Requeue: false},
+				err:    nil,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			r := NewReconciler(tc.params.client,
+				WithControllerEngine(&engine.ControllerEngine{}))
+			r.engine = tc.params.engine
+			got, err := r.Reconcile(tc.args.ctx, tc.args.req)
+
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.want.result, got); diff != "" {
+				t.Errorf("\n%s\nr.Reconcile(...): -want result, +got result:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/controller/pkg/runtime/migration_test.go b/internal/controller/pkg/runtime/migration_test.go
index 98da8a053..94412547b 100644
--- a/internal/controller/pkg/runtime/migration_test.go
+++ b/internal/controller/pkg/runtime/migration_test.go
@@ -242,7 +242,7 @@ func TestDeletingDeploymentSelectorMigrator_MigrateDeploymentSelector(t *testing
 						deploy.Namespace = testNamespaceName
 						deploy.Spec.Selector = &metav1.LabelSelector{
 							MatchLabels: map[string]string{
-								"pkg.crossplane.io/provider": "crossplane-provider-nop",
+								v1.LabelProvider: "crossplane-provider-nop",
 							},
 						}
 						return nil
@@ -276,7 +276,7 @@ func TestDeletingDeploymentSelectorMigrator_MigrateDeploymentSelector(t *testing
 						deploy.Namespace = testNamespaceName
 						deploy.Spec.Selector = &metav1.LabelSelector{
 							MatchLabels: map[string]string{
-								"pkg.crossplane.io/provider": "provider-nop", // Old format
+								v1.LabelProvider: "provider-nop", // Old format
 							},
 						}
 						return nil
@@ -290,7 +290,7 @@ func TestDeletingDeploymentSelectorMigrator_MigrateDeploymentSelector(t *testing
 							Spec: appsv1.DeploymentSpec{
 								Selector: &metav1.LabelSelector{
 									MatchLabels: map[string]string{
-										"pkg.crossplane.io/provider": "provider-nop",
+										v1.LabelProvider: "provider-nop",
 									},
 								},
 							},
@@ -330,7 +330,7 @@ func TestDeletingDeploymentSelectorMigrator_MigrateDeploymentSelector(t *testing
 						deploy.Namespace = testNamespaceName
 						deploy.Spec.Selector = &metav1.LabelSelector{
 							MatchLabels: map[string]string{
-								"pkg.crossplane.io/provider": "provider-nop", // Old format
+								v1.LabelProvider: "provider-nop", // Old format
 							},
 						}
 						return nil
diff --git a/internal/controller/pkg/runtime/runtime_test.go b/internal/controller/pkg/runtime/runtime_test.go
index 9b67c100e..f683db6a0 100644
--- a/internal/controller/pkg/runtime/runtime_test.go
+++ b/internal/controller/pkg/runtime/runtime_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package runtime
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -24,6 +25,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/intstr"
 	"k8s.io/utils/ptr"
 
@@ -38,10 +40,12 @@ const (
 	providerImage        = "xpkg.crossplane.io/crossplane/provider-foo:v1.2.3"
 	providerName         = "crossplane-provider-foo"
 	providerRevisionName = "provider-foo-1234"
+	providerRevisionUID  = "12345678-1234-1234-1234-123456789012"
 
 	functionImage        = "xpkg.crossplane.io/crossplane/function-foo:v1.2.3"
 	functionName         = "function-foo"
 	functionRevisionName = "function-foo-1234"
+	functionRevisionUID  = "98765432-1234-1234-1234-210987654321"
 
 	tlsServerSecretName = "tls-server-secret"
 	tlsClientSecretName = "tls-client-secret"
@@ -58,6 +62,7 @@ var (
 			Labels: map[string]string{
 				v1.LabelParentPackage: providerName,
 			},
+			UID: types.UID(providerRevisionUID),
 		},
 		Spec: v1.ProviderRevisionSpec{
 			PackageRevisionSpec: v1.PackageRevisionSpec{
@@ -82,6 +87,7 @@ var (
 			Labels: map[string]string{
 				v1.LabelParentPackage: functionName,
 			},
+			UID: types.UID(functionRevisionUID),
 		},
 		Spec: v1.FunctionRevisionSpec{
 			PackageRevisionSpec: v1.PackageRevisionSpec{
@@ -124,8 +130,8 @@ func TestRuntimeManifestBuilderDeployment(t *testing.T) {
 			},
 			want: want{
 				want: deploymentProvider(providerName, providerRevisionName, providerImage, DeploymentWithSelectors(map[string]string{
-					"pkg.crossplane.io/provider": providerName,
-					"pkg.crossplane.io/revision": providerRevisionName,
+					v1.LabelProvider: providerName,
+					v1.LabelRevision: providerRevisionName,
 				})),
 			},
 		},
@@ -176,8 +182,8 @@ func TestRuntimeManifestBuilderDeployment(t *testing.T) {
 			},
 			want: want{
 				want: deploymentProvider(providerName, providerRevisionName, providerImage, DeploymentWithSelectors(map[string]string{
-					"pkg.crossplane.io/provider": providerName,
-					"pkg.crossplane.io/revision": providerRevisionName,
+					v1.LabelProvider: providerName,
+					v1.LabelRevision: providerRevisionName,
 				}), func(deployment *appsv1.Deployment) {
 					deployment.Spec.Replicas = ptr.To[int32](3)
 					deployment.Spec.Template.Labels["k"] = "v"
@@ -217,8 +223,8 @@ func TestRuntimeManifestBuilderDeployment(t *testing.T) {
 			},
 			want: want{
 				want: deploymentProvider(providerName, providerRevisionName, providerImage, DeploymentWithSelectors(map[string]string{
-					"pkg.crossplane.io/provider": providerName,
-					"pkg.crossplane.io/revision": providerRevisionName,
+					v1.LabelProvider: providerName,
+					v1.LabelRevision: providerRevisionName,
 				}), func(deployment *appsv1.Deployment) {
 					deployment.Spec.Template.Annotations = map[string]string{
 						"prometheus.io/scrape": "false",
@@ -293,8 +299,8 @@ func TestRuntimeManifestBuilderDeployment(t *testing.T) {
 			},
 			want: want{
 				want: deploymentProvider(providerName, providerRevisionName, providerImage, DeploymentWithSelectors(map[string]string{
-					"pkg.crossplane.io/provider": providerName,
-					"pkg.crossplane.io/revision": providerRevisionName,
+					v1.LabelProvider: providerName,
+					v1.LabelRevision: providerRevisionName,
 				}), func(deployment *appsv1.Deployment) {
 					deployment.Name = "my-provider-foo"
 					deployment.Labels = map[string]string{
@@ -333,7 +339,7 @@ func TestRuntimeManifestBuilderDeployment(t *testing.T) {
 					namespace: namespace,
 				},
 				serviceAccountName: functionRevisionName,
-				overrides:          functionDeploymentOverrides(functionImage),
+				overrides:          functionDeploymentOverrides(functionRevision, functionImage),
 			},
 			want: want{
 				want: deploymentFunction(functionName, functionRevisionName, functionImage),
@@ -395,6 +401,7 @@ func TestRuntimeManifestBuilderService(t *testing.T) {
 								APIVersion:         "pkg.crossplane.io/v1",
 								Kind:               "ProviderRevision",
 								Name:               providerRevisionName,
+								UID:                types.UID(providerRevisionUID),
 								Controller:         ptr.To(true),
 								BlockOwnerDeletion: ptr.To(true),
 							},
@@ -402,8 +409,8 @@ func TestRuntimeManifestBuilderService(t *testing.T) {
 					},
 					Spec: corev1.ServiceSpec{
 						Selector: map[string]string{
-							"pkg.crossplane.io/provider": providerName,
-							"pkg.crossplane.io/revision": providerRevisionName,
+							v1.LabelProvider: providerName,
+							v1.LabelRevision: providerRevisionName,
 						},
 						Ports: []corev1.ServicePort{
 							{
@@ -438,6 +445,7 @@ func deploymentProvider(provider string, rev string, image string, overrides ...
 					APIVersion:         "pkg.crossplane.io/v1",
 					Kind:               "ProviderRevision",
 					Name:               rev,
+					UID:                types.UID(providerRevisionUID),
 					Controller:         ptr.To(true),
 					BlockOwnerDeletion: ptr.To(true),
 				},
@@ -447,8 +455,8 @@ func deploymentProvider(provider string, rev string, image string, overrides ...
 			Replicas: ptr.To[int32](1),
 			Selector: &metav1.LabelSelector{
 				MatchLabels: map[string]string{
-					"pkg.crossplane.io/revision": rev,
-					"pkg.crossplane.io/provider": provider,
+					v1.LabelRevision: rev,
+					v1.LabelProvider: provider,
 				},
 			},
 			Template: corev1.PodTemplateSpec{
@@ -459,8 +467,8 @@ func deploymentProvider(provider string, rev string, image string, overrides ...
 						"prometheus.io/path":   "/metrics",
 					},
 					Labels: map[string]string{
-						"pkg.crossplane.io/revision": rev,
-						"pkg.crossplane.io/provider": provider,
+						v1.LabelRevision: rev,
+						v1.LabelProvider: provider,
 					},
 				},
 				Spec: corev1.PodSpec{
@@ -502,6 +510,26 @@ func deploymentProvider(provider string, rev string, image string, overrides ...
 										},
 									},
 								},
+								{
+									Name: "PROVIDER_NAME",
+									ValueFrom: &corev1.EnvVarSource{
+										FieldRef: &corev1.ObjectFieldSelector{
+											FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelProvider),
+										},
+									},
+								},
+								{
+									Name: "REVISION_NAME",
+									ValueFrom: &corev1.EnvVarSource{
+										FieldRef: &corev1.ObjectFieldSelector{
+											FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelRevision),
+										},
+									},
+								},
+								{
+									Name:  "REVISION_UID",
+									Value: providerRevisionUID,
+								},
 								{
 									Name:  "ESS_TLS_CERTS_DIR",
 									Value: "$(TLS_CLIENT_CERTS_DIR)",
@@ -600,6 +628,7 @@ func deploymentFunction(function string, rev string, image string, overrides ...
 					APIVersion:         "pkg.crossplane.io/v1beta1",
 					Kind:               "FunctionRevision",
 					Name:               rev,
+					UID:                types.UID(functionRevisionUID),
 					Controller:         ptr.To(true),
 					BlockOwnerDeletion: ptr.To(true),
 				},
@@ -609,15 +638,15 @@ func deploymentFunction(function string, rev string, image string, overrides ...
 			Replicas: ptr.To[int32](1),
 			Selector: &metav1.LabelSelector{
 				MatchLabels: map[string]string{
-					"pkg.crossplane.io/revision": rev,
-					"pkg.crossplane.io/function": function,
+					v1.LabelRevision: rev,
+					v1.LabelFunction: function,
 				},
 			},
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
 					Labels: map[string]string{
-						"pkg.crossplane.io/revision": rev,
-						"pkg.crossplane.io/function": function,
+						v1.LabelRevision: rev,
+						v1.LabelFunction: function,
 					},
 				},
 				Spec: corev1.PodSpec{
@@ -647,6 +676,26 @@ func deploymentFunction(function string, rev string, image string, overrides ...
 									Name:  "TLS_SERVER_CERTS_DIR",
 									Value: "/tls/server",
 								},
+								{
+									Name: "FUNCTION_NAME",
+									ValueFrom: &corev1.EnvVarSource{
+										FieldRef: &corev1.ObjectFieldSelector{
+											FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelFunction),
+										},
+									},
+								},
+								{
+									Name: "REVISION_NAME",
+									ValueFrom: &corev1.EnvVarSource{
+										FieldRef: &corev1.ObjectFieldSelector{
+											FieldPath: fmt.Sprintf("metadata.labels['%s']", v1.LabelRevision),
+										},
+									},
+								},
+								{
+									Name:  "REVISION_UID",
+									Value: functionRevisionUID,
+								},
 							},
 							VolumeMounts: []corev1.VolumeMount{
 								{
diff --git a/internal/controller/rbac/provider/roles/roles_test.go b/internal/controller/rbac/provider/roles/roles_test.go
index bc8386447..98519487a 100644
--- a/internal/controller/rbac/provider/roles/roles_test.go
+++ b/internal/controller/rbac/provider/roles/roles_test.go
@@ -24,6 +24,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 
+	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 )
 
@@ -160,6 +161,85 @@ func TestRenderClusterRoles(t *testing.T) {
 				},
 			},
 		},
+		"SafeStart": {
+			reason: "A ProviderRevision with safe-start capability should add CRD permissions to view role.",
+			args: args{
+				pr: &v1.ProviderRevision{
+					ObjectMeta: metav1.ObjectMeta{Name: prName, UID: prUID},
+					Status: v1.ProviderRevisionStatus{
+						PackageRevisionStatus: v1.PackageRevisionStatus{
+							Capabilities: []string{pkgmetav1.ProviderCapabilitySafeStart},
+						},
+					},
+				},
+				resources: []Resource{
+					{
+						Group:  groupA,
+						Plural: pluralA,
+					},
+				},
+			},
+			want: []rbacv1.ClusterRole{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:            nameEdit,
+						OwnerReferences: []metav1.OwnerReference{crCtrlr},
+						Labels: map[string]string{
+							keyAggregateToCrossplane: valTrue,
+							keyAggregateToAdmin:      valTrue,
+							keyAggregateToEdit:       valTrue,
+						},
+					},
+					Rules: []rbacv1.PolicyRule{
+						{
+							APIGroups: []string{groupA},
+							Resources: []string{pluralA, pluralA + suffixStatus},
+							Verbs:     verbsEdit,
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:            nameView,
+						OwnerReferences: []metav1.OwnerReference{crCtrlr},
+						Labels: map[string]string{
+							keyAggregateToView: valTrue,
+						},
+					},
+					Rules: []rbacv1.PolicyRule{
+						{
+							APIGroups: []string{groupA},
+							Resources: []string{pluralA, pluralA + suffixStatus},
+							Verbs:     verbsView,
+						},
+						{
+							APIGroups: []string{"apiextensions.k8s.io"},
+							Resources: []string{"customresourcedefinitions"},
+							Verbs:     verbsView,
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:            nameSystem,
+						Labels:          map[string]string{keyProviderName: prName},
+						OwnerReferences: []metav1.OwnerReference{crCtrlr},
+					},
+					Rules: append([]rbacv1.PolicyRule{
+						{
+							APIGroups: []string{groupA},
+							Resources: []string{pluralA, pluralA + suffixStatus},
+							Verbs:     verbsSystem,
+						},
+						{
+							APIGroups: []string{groupA},
+							Resources: []string{rbacv1.ResourceAll + suffixFinalizers},
+							Verbs:     verbsUpdate,
+						},
+					}, rulesSystemExtra...),
+				},
+			},
+		},
 	}
 
 	for name, tc := range cases {
diff --git a/internal/converter/custom_to_managed_test.go b/internal/converter/custom_to_managed_test.go
new file mode 100644
index 000000000..fa0a9cd51
--- /dev/null
+++ b/internal/converter/custom_to_managed_test.go
@@ -0,0 +1,678 @@
+/*
+Copyright 2025 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 converter
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	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"
+
+	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+// invalidJSONObject is a runtime.Object that fails to marshal to JSON.
+type invalidJSONObject struct {
+	metav1.TypeMeta
+	metav1.ObjectMeta
+	Channel chan struct{} `json:"channel"` // channels cannot be marshaled to JSON
+}
+
+func (i *invalidJSONObject) DeepCopyObject() runtime.Object {
+	return &invalidJSONObject{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: customResourceDefinitionKind.GroupVersion().String(),
+			Kind:       customResourceDefinitionKind.Kind,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "invalid-json-object",
+		},
+		Channel: make(chan struct{}),
+	}
+}
+
+func (i *invalidJSONObject) GetObjectKind() schema.ObjectKind {
+	return i
+}
+
+func (i *invalidJSONObject) GroupVersionKind() schema.GroupVersionKind {
+	return customResourceDefinitionKind
+}
+
+func (i *invalidJSONObject) SetGroupVersionKind(gvk schema.GroupVersionKind) {
+	i.APIVersion = gvk.GroupVersion().String()
+	i.Kind = gvk.Kind
+}
+
+func TestCustomToManagedResourceDefinitions(t *testing.T) {
+	// Test CustomResourceDefinition
+	testCRD := &extv1.CustomResourceDefinition{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: extv1.SchemeGroupVersion.String(),
+			Kind:       customResourceDefinition,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "buckets.example.org",
+			Namespace: "test-namespace",
+		},
+		Spec: extv1.CustomResourceDefinitionSpec{
+			Group: "example.org",
+			Names: extv1.CustomResourceDefinitionNames{
+				Kind:     "Bucket",
+				Plural:   "buckets",
+				Singular: "bucket",
+			},
+			Scope: extv1.NamespaceScoped,
+			Versions: []extv1.CustomResourceDefinitionVersion{
+				{
+					Name:    "v1",
+					Served:  true,
+					Storage: true,
+				},
+			},
+		},
+	}
+
+	// Test unstructured CRD
+	testUnstructuredCRD := &unstructured.Unstructured{
+		Object: map[string]any{
+			"apiVersion": extv1.SchemeGroupVersion.String(),
+			"kind":       customResourceDefinition,
+			"metadata": map[string]any{
+				"name":      "instances.example.org",
+				"namespace": "test-namespace",
+			},
+			"spec": map[string]any{
+				"group": "example.org",
+				"names": map[string]any{
+					"kind":     "Instance",
+					"plural":   "instances",
+					"singular": "instance",
+				},
+				"scope": "Namespaced",
+				"versions": []any{
+					map[string]any{
+						"name":    "v1",
+						"served":  true,
+						"storage": true,
+					},
+				},
+			},
+		},
+	}
+
+	// Test non-CRD object
+	testNonCRD := &unstructured.Unstructured{
+		Object: map[string]any{
+			"apiVersion": "apps/v1",
+			"kind":       "Deployment",
+			"metadata": map[string]any{
+				"name":      "test-deployment",
+				"namespace": "test-namespace",
+			},
+		},
+	}
+
+	// Expected MRD from structured CRD (inactive)
+	expectedMRDFromCRDInactive := &v2alpha1.ManagedResourceDefinition{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "apiextensions.crossplane.io/v2alpha1",
+			Kind:       "ManagedResourceDefinition",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "buckets.example.org",
+			Namespace: "test-namespace",
+		},
+		Spec: v2alpha1.ManagedResourceDefinitionSpec{
+			CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+				Group: "example.org",
+				Names: extv1.CustomResourceDefinitionNames{
+					Kind:     "Bucket",
+					Plural:   "buckets",
+					Singular: "bucket",
+				},
+				Scope: "Namespaced",
+				Versions: []v2alpha1.CustomResourceDefinitionVersion{
+					{
+						Name:    "v1",
+						Served:  true,
+						Storage: true,
+					},
+				},
+			},
+		},
+	}
+
+	// Expected MRD from structured CRD (active)
+	expectedMRDFromCRDActive := &v2alpha1.ManagedResourceDefinition{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "apiextensions.crossplane.io/v2alpha1",
+			Kind:       "ManagedResourceDefinition",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "buckets.example.org",
+			Namespace: "test-namespace",
+		},
+		Spec: v2alpha1.ManagedResourceDefinitionSpec{
+			CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+				Group: "example.org",
+				Names: extv1.CustomResourceDefinitionNames{
+					Kind:     "Bucket",
+					Plural:   "buckets",
+					Singular: "bucket",
+				},
+				Scope: "Namespaced",
+				Versions: []v2alpha1.CustomResourceDefinitionVersion{
+					{
+						Name:    "v1",
+						Served:  true,
+						Storage: true,
+					},
+				},
+			},
+			State: v2alpha1.ManagedResourceDefinitionActive,
+		},
+	}
+
+	// Expected MRD from unstructured CRD (inactive)
+	expectedMRDFromUnstructuredInactive := &v2alpha1.ManagedResourceDefinition{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "apiextensions.crossplane.io/v2alpha1",
+			Kind:       "ManagedResourceDefinition",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "instances.example.org",
+			Namespace: "test-namespace",
+		},
+		Spec: v2alpha1.ManagedResourceDefinitionSpec{
+			CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+				Group: "example.org",
+				Names: extv1.CustomResourceDefinitionNames{
+					Kind:     "Instance",
+					Plural:   "instances",
+					Singular: "instance",
+				},
+				Scope: "Namespaced",
+				Versions: []v2alpha1.CustomResourceDefinitionVersion{
+					{
+						Name:    "v1",
+						Served:  true,
+						Storage: true,
+					},
+				},
+			},
+		},
+	}
+
+	// Expected MRD from unstructured CRD (active)
+	expectedMRDFromUnstructuredActive := &v2alpha1.ManagedResourceDefinition{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "apiextensions.crossplane.io/v2alpha1",
+			Kind:       "ManagedResourceDefinition",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "instances.example.org",
+			Namespace: "test-namespace",
+		},
+		Spec: v2alpha1.ManagedResourceDefinitionSpec{
+			CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+				Group: "example.org",
+				Names: extv1.CustomResourceDefinitionNames{
+					Kind:     "Instance",
+					Plural:   "instances",
+					Singular: "instance",
+				},
+				Scope: "Namespaced",
+				Versions: []v2alpha1.CustomResourceDefinitionVersion{
+					{
+						Name:    "v1",
+						Served:  true,
+						Storage: true,
+					},
+				},
+			},
+			State: v2alpha1.ManagedResourceDefinitionActive,
+		},
+	}
+
+	type args struct {
+		defaultActive bool
+		objects       []runtime.Object
+	}
+	type want struct {
+		objects []runtime.Object
+		err     error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"EmptyInput": {
+			reason: "Should return empty slice for empty input",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{},
+			},
+			want: want{
+				objects: []runtime.Object{},
+			},
+		},
+		"NonCRDObject": {
+			reason: "Should leave non-CRD objects unchanged",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{testNonCRD},
+			},
+			want: want{
+				objects: []runtime.Object{testNonCRD},
+			},
+		},
+		"SingleStructuredCRDInactive": {
+			reason: "Should convert single structured CRD to MRD (inactive)",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{testCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromCRDInactive},
+			},
+		},
+		"SingleStructuredCRDActive": {
+			reason: "Should convert single structured CRD to MRD (active)",
+			args: args{
+				defaultActive: true,
+				objects:       []runtime.Object{testCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromCRDActive},
+			},
+		},
+		"SingleUnstructuredCRDInactive": {
+			reason: "Should convert single unstructured CRD to MRD (inactive)",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{testUnstructuredCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromUnstructuredInactive},
+			},
+		},
+		"SingleUnstructuredCRDActive": {
+			reason: "Should convert single unstructured CRD to MRD (active)",
+			args: args{
+				defaultActive: true,
+				objects:       []runtime.Object{testUnstructuredCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromUnstructuredActive},
+			},
+		},
+		"MixedObjectsInactive": {
+			reason: "Should convert only CRD objects, leaving others unchanged (inactive)",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{testCRD, testNonCRD, testUnstructuredCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromCRDInactive, testNonCRD, expectedMRDFromUnstructuredInactive},
+			},
+		},
+		"MixedObjectsActive": {
+			reason: "Should convert only CRD objects, leaving others unchanged (active)",
+			args: args{
+				defaultActive: true,
+				objects:       []runtime.Object{testCRD, testNonCRD, testUnstructuredCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromCRDActive, testNonCRD, expectedMRDFromUnstructuredActive},
+			},
+		},
+		"MultipleCRDsInactive": {
+			reason: "Should convert multiple CRDs to MRDs (inactive)",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{testCRD, testUnstructuredCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromCRDInactive, expectedMRDFromUnstructuredInactive},
+			},
+		},
+		"MultipleCRDsActive": {
+			reason: "Should convert multiple CRDs to MRDs (active)",
+			args: args{
+				defaultActive: true,
+				objects:       []runtime.Object{testCRD, testUnstructuredCRD},
+			},
+			want: want{
+				objects: []runtime.Object{expectedMRDFromCRDActive, expectedMRDFromUnstructuredActive},
+			},
+		},
+		"InvalidJSON": {
+			reason: "Should return error when JSON marshaling fails",
+			args: args{
+				defaultActive: false,
+				objects:       []runtime.Object{&invalidJSONObject{}},
+			},
+			want: want{
+				objects: []runtime.Object{&invalidJSONObject{}},
+				// We expect an error but don't care about the exact type since it's wrapped
+				err: errors.New("some error"),
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			gotObjects, err := CustomToManagedResourceDefinitions(tc.args.defaultActive, tc.args.objects...)
+
+			if tc.want.err != nil {
+				// Just check that we got an error when we expected one
+				if err == nil {
+					t.Errorf("\n%s\nCustomToManagedResourceDefinitions(...): expected error but got none", tc.reason)
+				}
+			} else {
+				if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+					t.Errorf("\n%s\nCustomToManagedResourceDefinitions(...): -want error, +got error:\n%s", tc.reason, diff)
+				}
+			}
+			if diff := cmp.Diff(tc.want.objects, gotObjects); diff != "" {
+				t.Errorf("\n%s\nCustomToManagedResourceDefinitions(...): -want objects, +got objects:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestConvertCRDToMRD(t *testing.T) {
+	type args struct {
+		defaultActive bool
+		in            map[string]any
+	}
+	type want struct {
+		out *v2alpha1.ManagedResourceDefinition
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"BasicCRDInactive": {
+			reason: "Should convert basic CRD to MRD format (inactive)",
+			args: args{
+				defaultActive: false,
+				in: map[string]any{
+					"apiVersion": extv1.SchemeGroupVersion.String(),
+					"kind":       customResourceDefinition,
+					"metadata": map[string]any{
+						"name": "buckets.example.org",
+					},
+					"spec": map[string]any{
+						"group": "example.org",
+						"names": map[string]any{
+							"kind":   "Bucket",
+							"plural": "buckets",
+						},
+					},
+				},
+			},
+			want: want{
+				out: &v2alpha1.ManagedResourceDefinition{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: "apiextensions.crossplane.io/v2alpha1",
+						Kind:       "ManagedResourceDefinition",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "buckets.example.org",
+					},
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.org",
+							Names: extv1.CustomResourceDefinitionNames{
+								Kind:   "Bucket",
+								Plural: "buckets",
+							},
+						},
+					},
+				},
+			},
+		},
+		"BasicCRDActive": {
+			reason: "Should convert basic CRD to MRD format (active)",
+			args: args{
+				defaultActive: true,
+				in: map[string]any{
+					"apiVersion": extv1.SchemeGroupVersion.String(),
+					"kind":       customResourceDefinition,
+					"metadata": map[string]any{
+						"name": "buckets.example.org",
+					},
+					"spec": map[string]any{
+						"group": "example.org",
+						"names": map[string]any{
+							"kind":   "Bucket",
+							"plural": "buckets",
+						},
+					},
+				},
+			},
+			want: want{
+				out: &v2alpha1.ManagedResourceDefinition{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: "apiextensions.crossplane.io/v2alpha1",
+						Kind:       "ManagedResourceDefinition",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "buckets.example.org",
+					},
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.org",
+							Names: extv1.CustomResourceDefinitionNames{
+								Kind:   "Bucket",
+								Plural: "buckets",
+							},
+						},
+						State: v2alpha1.ManagedResourceDefinitionActive,
+					},
+				},
+			},
+		},
+		"CRDWithComplexSpecActive": {
+			reason: "Should convert CRD with complex spec to MRD, preserving all fields (active)",
+			args: args{
+				defaultActive: true,
+				in: map[string]any{
+					"apiVersion": extv1.SchemeGroupVersion.String(),
+					"kind":       customResourceDefinition,
+					"metadata": map[string]any{
+						"name":      "databases.example.org",
+						"namespace": "test-namespace",
+						"labels": map[string]any{
+							"app": "test",
+						},
+					},
+					"spec": map[string]any{
+						"group": "example.org",
+						"names": map[string]any{
+							"kind":     "Database",
+							"plural":   "databases",
+							"singular": "database",
+						},
+						"scope": "Namespaced",
+						"versions": []any{
+							map[string]any{
+								"name":    "v1",
+								"served":  true,
+								"storage": true,
+								"schema": map[string]any{
+									"openAPIV3Schema": map[string]any{
+										"type": "object",
+										"properties": map[string]any{
+											"spec": map[string]any{
+												"type": "object",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				out: &v2alpha1.ManagedResourceDefinition{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: "apiextensions.crossplane.io/v2alpha1",
+						Kind:       "ManagedResourceDefinition",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "databases.example.org",
+						Namespace: "test-namespace",
+						Labels: map[string]string{
+							"app": "test",
+						},
+					},
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						CustomResourceDefinitionSpec: v2alpha1.CustomResourceDefinitionSpec{
+							Group: "example.org",
+							Names: extv1.CustomResourceDefinitionNames{
+								Kind:     "Database",
+								Plural:   "databases",
+								Singular: "database",
+							},
+							Scope: "Namespaced",
+							Versions: []v2alpha1.CustomResourceDefinitionVersion{
+								{
+									Name:    "v1",
+									Served:  true,
+									Storage: true,
+									Schema: &v2alpha1.CustomResourceValidation{
+										OpenAPIV3Schema: runtime.RawExtension{
+											Raw: []byte(`{"properties":{"spec":{"type":"object"}},"type":"object"}`),
+										},
+									},
+								},
+							},
+						},
+						State: v2alpha1.ManagedResourceDefinitionActive,
+					},
+				},
+			},
+		},
+		"MinimalCRDInactive": {
+			reason: "Should convert minimal CRD to MRD (inactive)",
+			args: args{
+				defaultActive: false,
+				in: map[string]any{
+					"apiVersion": extv1.SchemeGroupVersion.String(),
+					"kind":       customResourceDefinition,
+					"metadata": map[string]any{
+						"name": "minimal.example.org",
+					},
+					"spec": map[string]any{},
+				},
+			},
+			want: want{
+				out: &v2alpha1.ManagedResourceDefinition{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: "apiextensions.crossplane.io/v2alpha1",
+						Kind:       "ManagedResourceDefinition",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "minimal.example.org",
+					},
+				},
+			},
+		},
+		"MinimalCRDActive": {
+			reason: "Should convert minimal CRD to MRD (active)",
+			args: args{
+				defaultActive: true,
+				in: map[string]any{
+					"apiVersion": extv1.SchemeGroupVersion.String(),
+					"kind":       customResourceDefinition,
+					"metadata": map[string]any{
+						"name": "minimal.example.org",
+					},
+					"spec": map[string]any{},
+				},
+			},
+			want: want{
+				out: &v2alpha1.ManagedResourceDefinition{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: "apiextensions.crossplane.io/v2alpha1",
+						Kind:       "ManagedResourceDefinition",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "minimal.example.org",
+					},
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						State: v2alpha1.ManagedResourceDefinitionActive,
+					},
+				},
+			},
+		},
+		"EmptyInputActive": {
+			reason: "Should handle empty input gracefully (active)",
+			args: args{
+				defaultActive: true,
+				in:            map[string]any{},
+			},
+			want: want{
+				out: &v2alpha1.ManagedResourceDefinition{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: "apiextensions.crossplane.io/v2alpha1",
+						Kind:       "ManagedResourceDefinition",
+					},
+					Spec: v2alpha1.ManagedResourceDefinitionSpec{
+						State: v2alpha1.ManagedResourceDefinitionActive,
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got, err := convertCRDToMRD(tc.args.defaultActive, tc.args.in)
+
+			if tc.want.err != nil {
+				// Just check that we got an error when we expected one
+				if err == nil {
+					t.Errorf("\n%s\nconvertCRDToMRD(...): expected error but got none", tc.reason)
+				}
+			} else {
+				if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+					t.Errorf("\n%s\nconvertCRDToMRD(...): -want error, +got error:\n%s", tc.reason, diff)
+				}
+			}
+			if diff := cmp.Diff(tc.want.out, got); diff != "" {
+				t.Errorf("\n%s\nconvertCRDToMRD(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/initializer/managed_resource_activation_policy_test.go b/internal/initializer/managed_resource_activation_policy_test.go
new file mode 100644
index 000000000..52caa9fce
--- /dev/null
+++ b/internal/initializer/managed_resource_activation_policy_test.go
@@ -0,0 +1,122 @@
+/*
+Copyright 2021 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 initializer
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	"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/test"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+)
+
+func TestDefaultManagedResourceActivationPolicy(t *testing.T) {
+	type args struct {
+		activations []v2alpha1.ActivationPolicy
+		kube        client.Client
+	}
+
+	type want struct {
+		err   error
+		nilFn bool
+	}
+
+	cases := map[string]struct {
+		args
+		want
+	}{
+		"NoActivation": {
+			args: args{},
+			want: want{
+				nilFn: true,
+			},
+		},
+		"FailedToCreate": {
+			args: args{
+				activations: []v2alpha1.ActivationPolicy{"*"},
+				kube: &test.MockClient{
+					MockCreate: func(_ context.Context, obj client.Object, _ ...client.CreateOption) error {
+						mrap, ok := obj.(*v2alpha1.ManagedResourceActivationPolicy)
+						if !ok {
+							t.Errorf("Expected ManagedResourceActivationPolicy, got %T", obj)
+							return nil
+						}
+						expectedActivations := []v2alpha1.ActivationPolicy{"*"}
+						if diff := cmp.Diff(expectedActivations, mrap.Spec.Activations); diff != "" {
+							t.Errorf("Activations mismatch (-want +got):\n%s", diff)
+						}
+						return errBoom
+					},
+				},
+			},
+			want: want{
+				err: errors.Wrap(errBoom, "cannot create ManagedResourceActivationPolicy \"default\""),
+			},
+		},
+		"SuccessCreated": {
+			args: args{
+				activations: []v2alpha1.ActivationPolicy{"policy1", "policy2", "*.example.com"},
+				kube: &test.MockClient{
+					MockCreate: func(_ context.Context, obj client.Object, _ ...client.CreateOption) error {
+						mrap, ok := obj.(*v2alpha1.ManagedResourceActivationPolicy)
+						if !ok {
+							t.Errorf("Expected ManagedResourceActivationPolicy, got %T", obj)
+							return nil
+						}
+						expectedActivations := []v2alpha1.ActivationPolicy{"policy1", "policy2", "*.example.com"}
+						if diff := cmp.Diff(expectedActivations, mrap.Spec.Activations); diff != "" {
+							t.Errorf("Activations mismatch (-want +got):\n%s", diff)
+						}
+						return nil
+					},
+				},
+			},
+		},
+		"SuccessAlreadyExists": {
+			args: args{
+				activations: []v2alpha1.ActivationPolicy{"*"},
+				kube: &test.MockClient{
+					MockCreate: func(_ context.Context, _ client.Object, _ ...client.CreateOption) error {
+						return kerrors.NewAlreadyExists(schema.GroupResource{}, "default")
+					},
+				},
+			},
+		},
+	}
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			fn := DefaultManagedResourceActivationPolicy(tc.activations...)
+			if fn == nil {
+				if !tc.nilFn {
+					t.Errorf("\n%s\nUnexpected nil function", name)
+				}
+				return
+			}
+			err := fn(context.TODO(), tc.kube)
+			if diff := cmp.Diff(tc.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nRun(...): -want err, +got err:\n%s", name, diff)
+			}
+		})
+	}
+}
diff --git a/internal/names/generate_test.go b/internal/names/generate_test.go
index c68b5529f..c5bc71fbd 100644
--- a/internal/names/generate_test.go
+++ b/internal/names/generate_test.go
@@ -21,6 +21,7 @@ import (
 	"strconv"
 	"testing"
 
+	"github.com/aws/smithy-go/ptr"
 	"github.com/google/go-cmp/cmp"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,6 +32,8 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
+
+	"github.com/crossplane/crossplane/internal/xcrd"
 )
 
 func TestGenerateName(t *testing.T) {
@@ -110,6 +113,27 @@ func TestGenerateName(t *testing.T) {
 				}},
 			},
 		},
+		"SuccessMissingOwner": {
+			reason: "If no owner, use the random name generator",
+			client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, "cool-resource-42"))},
+			args: args{
+				cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "cool-resource-",
+					Annotations: map[string]string{
+						xcrd.AnnotationKeyCompositionResourceName: "pipeline-name-of-cool-resource",
+					},
+				}},
+			},
+			want: want{
+				cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "cool-resource-",
+					Name:         "cool-resource-42",
+					Annotations: map[string]string{
+						xcrd.AnnotationKeyCompositionResourceName: "pipeline-name-of-cool-resource",
+					},
+				}},
+			},
+		},
 		"SuccessAfterConflict": {
 			reason: "Name is found on second try",
 			client: &test.MockClient{MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
@@ -145,6 +169,76 @@ func TestGenerateName(t *testing.T) {
 				err: errors.New(errGenerateName),
 			},
 		},
+		"SuccessCompositeTruncated": {
+			reason: "Is annotated and owned should use ChildName and not be random, but if all this is too long, it will be shortened",
+			client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, "cool-resource-42"))},
+			args: args{
+				cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "cool-resource-with-a-really-long-name-that-can-not-fit-all-in-one-place-",
+					OwnerReferences: []metav1.OwnerReference{{
+						APIVersion: "Foo/v1",
+						Kind:       "Bar",
+						Name:       "parent",
+						UID:        "75e4a668-035f-4ce8-8c45-f4d3ac850155",
+						Controller: ptr.Bool(true),
+					}},
+					Annotations: map[string]string{
+						xcrd.AnnotationKeyCompositionResourceName: "pipeline-name-of-cool-resource",
+					},
+				}},
+			},
+			want: want{
+				cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "cool-resource-with-a-really-long-name-that-can-not-fit-all-in-one-place-",
+					Name:         "cool-resource-with-a-really-lonab455e7d35d099adea15e918d64db893",
+					OwnerReferences: []metav1.OwnerReference{{
+						APIVersion: "Foo/v1",
+						Kind:       "Bar",
+						Name:       "parent",
+						UID:        "75e4a668-035f-4ce8-8c45-f4d3ac850155",
+						Controller: ptr.Bool(true),
+					}},
+					Annotations: map[string]string{
+						xcrd.AnnotationKeyCompositionResourceName: "pipeline-name-of-cool-resource",
+					},
+				}},
+			},
+		},
+		"SuccessComposite": {
+			reason: "Is annotated and owned should use ChildName and not be random",
+			client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, "cool-resource-42"))},
+			args: args{
+				cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "cool-resource-",
+					OwnerReferences: []metav1.OwnerReference{{
+						APIVersion: "Foo/v1",
+						Kind:       "Bar",
+						Name:       "parent",
+						UID:        "75e4a668-035f-4ce8-8c45-f4d3ac850155",
+						Controller: ptr.Bool(true),
+					}},
+					Annotations: map[string]string{
+						xcrd.AnnotationKeyCompositionResourceName: "kid1",
+					},
+				}},
+			},
+			want: want{
+				cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "cool-resource-",
+					Name:         "cool-resource-f4d3ac850155-kid1",
+					OwnerReferences: []metav1.OwnerReference{{
+						APIVersion: "Foo/v1",
+						Kind:       "Bar",
+						Name:       "parent",
+						UID:        "75e4a668-035f-4ce8-8c45-f4d3ac850155",
+						Controller: ptr.Bool(true),
+					}},
+					Annotations: map[string]string{
+						xcrd.AnnotationKeyCompositionResourceName: "kid1",
+					},
+				}},
+			},
+		},
 	}
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
diff --git a/internal/names/names_test.go b/internal/names/names_test.go
new file mode 100644
index 000000000..9d949ea39
--- /dev/null
+++ b/internal/names/names_test.go
@@ -0,0 +1,97 @@
+/*
+Copyright 2025 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.
+*/
+
+// Forked from https://github.com/knative/pkg/blob/ee1db869c7ef25eb4ac5c9ba0ab73fdc3f1b9dfa/kmeta/names_test.go
+
+/*
+copyright 2019 the knative authors
+
+licensed under the apache license, version 2.0 (the "license");
+you may not use this file except in compliance with the license.
+you may obtain a copy of the license at
+
+    http://www.apache.org/licenses/license-2.0
+
+unless required by applicable law or agreed to in writing, software
+distributed under the license is distributed on an "as is" basis,
+without warranties or conditions of any kind, either express or implied.
+see the license for the specific language governing permissions and
+limitations under the license.
+*/
+
+package names
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"k8s.io/apimachinery/pkg/util/validation"
+)
+
+func TestChildName(t *testing.T) {
+	tests := []struct {
+		parent string
+		suffix string
+		want   string
+	}{
+		{
+			parent: "asdf",
+			suffix: "-deployment",
+			want:   "asdf-deployment",
+		}, {
+			parent: strings.Repeat("f", 63),
+			suffix: "-deployment",
+			want:   "ffffffffffffffffffff105d7597f637e83cc711605ac3ea4957-deployment",
+		}, {
+			parent: strings.Repeat("f", 63),
+			suffix: "-deploy",
+			want:   "ffffffffffffffffffffffff105d7597f637e83cc711605ac3ea4957-deploy",
+		}, {
+			parent: strings.Repeat("f", 63),
+			suffix: strings.Repeat("f", 63),
+			want:   "fffffffffffffffffffffffffffffff0502661254f13c89973cb3a83e0cbec0",
+		}, {
+			parent: "a",
+			suffix: strings.Repeat("f", 63),
+			want:   "ab5cfd486935decbc0d305799f4ce4414ffffffffffffffffffffffffffffff",
+		}, {
+			parent: strings.Repeat("b", 32),
+			suffix: strings.Repeat("f", 32),
+			want:   "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb329c7c81b9ab3ba71aa139066aa5625d",
+		}, {
+			parent: "aaaa",
+			suffix: strings.Repeat("b---a", 20),
+			want:   "aaaa7a3f7966594e3f0849720eced8212c18b---ab---ab---ab---ab---ab",
+		}, {
+			parent: strings.Repeat("a", 17),
+			suffix: "a.-.-.-.-.-.-.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+			want:   "aaaaaaaaaaaaaaaaaa1eb10dc911444f8434af83b7225442da",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.parent+"-"+test.suffix, func(t *testing.T) {
+			got, want := ChildName(test.parent, test.suffix), test.want
+			if errs := validation.IsDNS1123Subdomain(got); len(errs) != 0 {
+				t.Errorf("Invalid DNS1123 Subdomain %s\n\n Errors: %v", got, errs)
+			}
+			if got != want {
+				t.Errorf("%s-%s: got: %63s want: %63s\ndiff:%s", test.parent, test.suffix, got, want, cmp.Diff(want, got))
+			}
+		})
+	}
+}
diff --git a/internal/ops/lifecycle/operations_test.go b/internal/ops/lifecycle/operations_test.go
new file mode 100644
index 000000000..f76d0c05c
--- /dev/null
+++ b/internal/ops/lifecycle/operations_test.go
@@ -0,0 +1,826 @@
+/*
+Copyright 2025 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 lifecycle
+
+import (
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+
+	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+)
+
+func TestLatestCreateTime(t *testing.T) {
+	now := time.Now()
+	earlier := now.Add(-time.Hour)
+	later := now.Add(time.Hour)
+
+	type args struct {
+		ops []v1alpha1.Operation
+	}
+	type want struct {
+		latest time.Time
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"EmptySlice": {
+			reason: "Should return zero time for empty slice",
+			args: args{
+				ops: []v1alpha1.Operation{},
+			},
+			want: want{
+				latest: time.Time{},
+			},
+		},
+		"SingleOperation": {
+			reason: "Should return the single operation's creation time",
+			args: args{
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							CreationTimestamp: metav1.Time{Time: now},
+						},
+					},
+				},
+			},
+			want: want{
+				latest: now,
+			},
+		},
+		"MultipleOperations": {
+			reason: "Should return the latest creation time from multiple operations",
+			args: args{
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							CreationTimestamp: metav1.Time{Time: later},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							CreationTimestamp: metav1.Time{Time: now},
+						},
+					},
+				},
+			},
+			want: want{
+				latest: later,
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := LatestCreateTime(tc.args.ops...)
+			if diff := cmp.Diff(tc.want.latest, got); diff != "" {
+				t.Errorf("\n%s\nLatestCreateTime(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestLatestSucceededTransitionTime(t *testing.T) {
+	now := time.Now()
+	earlier := now.Add(-time.Hour)
+	later := now.Add(time.Hour)
+
+	type args struct {
+		ops []v1alpha1.Operation
+	}
+	type want struct {
+		latest time.Time
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"EmptySlice": {
+			reason: "Should return zero time for empty slice",
+			args: args{
+				ops: []v1alpha1.Operation{},
+			},
+			want: want{
+				latest: time.Time{},
+			},
+		},
+		"SingleOperation": {
+			reason: "Should return the single operation's succeeded transition time",
+			args: args{
+				ops: []v1alpha1.Operation{
+					{
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:               v1alpha1.TypeSucceeded,
+										LastTransitionTime: metav1.Time{Time: now},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				latest: now,
+			},
+		},
+		"MultipleOperations": {
+			reason: "Should return the latest succeeded transition time from multiple operations",
+			args: args{
+				ops: []v1alpha1.Operation{
+					{
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:               v1alpha1.TypeSucceeded,
+										LastTransitionTime: metav1.Time{Time: earlier},
+									},
+								},
+							},
+						},
+					},
+					{
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:               v1alpha1.TypeSucceeded,
+										LastTransitionTime: metav1.Time{Time: later},
+									},
+								},
+							},
+						},
+					},
+					{
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:               v1alpha1.TypeSucceeded,
+										LastTransitionTime: metav1.Time{Time: now},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				latest: later,
+			},
+		},
+		"NoSucceededConditions": {
+			reason: "Should return zero time when no operations have succeeded conditions",
+			args: args{
+				ops: []v1alpha1.Operation{
+					{
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:               "Other",
+										LastTransitionTime: metav1.Time{Time: now},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				latest: time.Time{},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := LatestSucceededTransitionTime(tc.args.ops...)
+			if diff := cmp.Diff(tc.want.latest, got); diff != "" {
+				t.Errorf("\n%s\nLatestSucceededTransitionTime(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestWithReason(t *testing.T) {
+	type args struct {
+		reason xpv1.ConditionReason
+		ops    []v1alpha1.Operation
+	}
+	type want struct {
+		filtered []v1alpha1.Operation
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"EmptySlice": {
+			reason: "Should return empty slice for empty input",
+			args: args{
+				reason: v1alpha1.ReasonPipelineSuccess,
+				ops:    []v1alpha1.Operation{},
+			},
+			want: want{
+				filtered: []v1alpha1.Operation{},
+			},
+		},
+		"NoMatches": {
+			reason: "Should return empty slice when no operations match the reason",
+			args: args{
+				reason: v1alpha1.ReasonPipelineSuccess,
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op1"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Reason: v1alpha1.ReasonPipelineRunning,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				filtered: []v1alpha1.Operation{},
+			},
+		},
+		"SomeMatches": {
+			reason: "Should return only operations that match the specified reason",
+			args: args{
+				reason: v1alpha1.ReasonPipelineSuccess,
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op1"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Reason: v1alpha1.ReasonPipelineSuccess,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op2"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Reason: v1alpha1.ReasonPipelineRunning,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op3"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Reason: v1alpha1.ReasonPipelineSuccess,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				filtered: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op1"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Reason: v1alpha1.ReasonPipelineSuccess,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op3"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Reason: v1alpha1.ReasonPipelineSuccess,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := WithReason(tc.args.reason, tc.args.ops...)
+			if diff := cmp.Diff(tc.want.filtered, got); diff != "" {
+				t.Errorf("\n%s\nWithReason(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestMarkGarbage(t *testing.T) {
+	now := time.Now()
+	earlier := now.Add(-time.Hour)
+	latest := now.Add(time.Hour)
+
+	type args struct {
+		keepSucceeded int32
+		keepFailed    int32
+		ops           []v1alpha1.Operation
+	}
+	type want struct {
+		del []v1alpha1.Operation
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"ZeroLimits": {
+			reason: "Should delete all operations when both limits are zero",
+			args: args{
+				keepSucceeded: 0,
+				keepFailed:    0,
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op1"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op2"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				del: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op1"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{Name: "op2"},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"KeepSucceededOnly": {
+			reason: "Should delete excess succeeded operations beyond the specified limit",
+			args: args{
+				keepSucceeded: 1,
+				keepFailed:    0,
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op1",
+							CreationTimestamp: metav1.Time{Time: latest},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op2",
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op3",
+							CreationTimestamp: metav1.Time{Time: now},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				del: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op2",
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op3",
+							CreationTimestamp: metav1.Time{Time: now},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"KeepFailedOnly": {
+			reason: "Should delete excess failed operations beyond the specified limit",
+			args: args{
+				keepSucceeded: 0,
+				keepFailed:    1,
+				ops: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op1",
+							CreationTimestamp: metav1.Time{Time: latest},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op2",
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				del: []v1alpha1.Operation{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "op2",
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"MixedScenario": {
+			reason: "Should delete excess succeeded operations beyond the specified limit",
+			args: args{
+				keepSucceeded: 2,
+				keepFailed:    0,
+				ops: []v1alpha1.Operation{
+					// 3 succeeded operations (latest first after sorting)
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "succeeded1",
+							CreationTimestamp: metav1.Time{Time: latest},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "succeeded2",
+							CreationTimestamp: metav1.Time{Time: now},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "succeeded3",
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+					// 1 failed operation (should be deleted - keepFailed=0)
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "failed1",
+							CreationTimestamp: metav1.Time{Time: now.Add(-30 * time.Minute)},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+					// 1 running operation (should be kept)
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "running1",
+							CreationTimestamp: metav1.Time{Time: now.Add(-15 * time.Minute)},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionUnknown,
+									},
+								},
+							},
+						},
+					},
+					// 1 operation with no condition (should be kept)
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "no-condition",
+							CreationTimestamp: metav1.Time{Time: now.Add(-45 * time.Minute)},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{},
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				del: []v1alpha1.Operation{
+					// Failed operation (keepFailed=0 means delete all failed)
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "failed1",
+							CreationTimestamp: metav1.Time{Time: now.Add(-30 * time.Minute)},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionFalse,
+									},
+								},
+							},
+						},
+					},
+					// Oldest succeeded operation (beyond the 2 we keep)
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "succeeded3",
+							CreationTimestamp: metav1.Time{Time: earlier},
+						},
+						Status: v1alpha1.OperationStatus{
+							ConditionedStatus: xpv1.ConditionedStatus{
+								Conditions: []xpv1.Condition{
+									{
+										Type:   v1alpha1.TypeSucceeded,
+										Status: corev1.ConditionTrue,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			del := MarkGarbage(tc.args.keepSucceeded, tc.args.keepFailed, tc.args.ops...)
+			if diff := cmp.Diff(tc.want.del, del, cmpopts.SortSlices(func(a, b v1alpha1.Operation) bool {
+				return a.Name < b.Name
+			})); diff != "" {
+				t.Errorf("\n%s\nMarkGarbage(...) del: -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestRunningOperationRefs(t *testing.T) {
+	type args struct {
+		running []string
+	}
+	type want struct {
+		refs []v1alpha1.RunningOperationRef
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"Empty": {
+			reason: "Should return empty slice for empty input",
+			args: args{
+				running: []string{},
+			},
+			want: want{
+				refs: []v1alpha1.RunningOperationRef{},
+			},
+		},
+		"Single": {
+			reason: "Should convert single name to RunningOperationRef",
+			args: args{
+				running: []string{"op1"},
+			},
+			want: want{
+				refs: []v1alpha1.RunningOperationRef{
+					{Name: "op1"},
+				},
+			},
+		},
+		"Multiple": {
+			reason: "Should convert multiple names to RunningOperationRefs",
+			args: args{
+				running: []string{"op1", "op2", "op3"},
+			},
+			want: want{
+				refs: []v1alpha1.RunningOperationRef{
+					{Name: "op1"},
+					{Name: "op2"},
+					{Name: "op3"},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := RunningOperationRefs(tc.args.running)
+			if diff := cmp.Diff(tc.want.refs, got); diff != "" {
+				t.Errorf("\n%s\nRunningOperationRefs(...): -want, +got:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/xcrd/crd_test.go b/internal/xcrd/crd_test.go
index 9d966e07e..ada8288bb 100644
--- a/internal/xcrd/crd_test.go
+++ b/internal/xcrd/crd_test.go
@@ -431,18 +431,6 @@ func TestForCompositeResource(t *testing.T) {
 														},
 													},
 												},
-												"crossplane": {
-													Description: "Indicates how Crossplane is reconciling this composite resource",
-													Type:        "object",
-													Properties: map[string]extv1.JSONSchemaProps{
-														"connectionDetails": {
-															Type: "object",
-															Properties: map[string]extv1.JSONSchemaProps{
-																"lastPublishedTime": {Type: "string", Format: "date-time"},
-															},
-														},
-													},
-												},
 											},
 											XValidations: extv1.ValidationRules{
 												{
diff --git a/internal/xpkg/lint_test.go b/internal/xpkg/lint_test.go
index 008ec1b40..a43cab63b 100644
--- a/internal/xpkg/lint_test.go
+++ b/internal/xpkg/lint_test.go
@@ -94,6 +94,16 @@ metadata:
 
 	v1alpha1OpBytes = []byte(`apiVersion: ops.crossplane.io/v1alpha1
 kind: Operation
+metadata:
+  name: test`)
+
+	v1alpha1CronOpBytes = []byte(`apiVersion: ops.crossplane.io/v1alpha1
+kind: CronOperation
+metadata:
+  name: test`)
+
+	v1alpha1WatchOpBytes = []byte(`apiVersion: ops.crossplane.io/v1alpha1
+kind: WatchOperation
 metadata:
   name: test`)
 
@@ -119,6 +129,10 @@ metadata:
 	_                = yaml.Unmarshal(v1CompBytes, v1Comp)
 	v1alpha1Op       = &v1alpha1.Operation{}
 	_                = yaml.Unmarshal(v1alpha1OpBytes, v1alpha1Op)
+	v1alpha1CronOp   = &v1alpha1.CronOperation{}
+	_                = yaml.Unmarshal(v1alpha1CronOpBytes, v1alpha1CronOp)
+	v1alpha1WatchOp  = &v1alpha1.WatchOperation{}
+	_                = yaml.Unmarshal(v1alpha1WatchOpBytes, v1alpha1WatchOp)
 
 	meta, _ = BuildMetaScheme()
 	obj, _  = BuildObjectScheme()
@@ -518,3 +532,57 @@ func TestIsOperation(t *testing.T) {
 		})
 	}
 }
+
+func TestIsCronOperation(t *testing.T) {
+	cases := map[string]struct {
+		reason string
+		obj    runtime.Object
+		err    error
+	}{
+		"v1": {
+			reason: "Should not return error if object is a cron operation.",
+			obj:    v1alpha1CronOp,
+		},
+		"ErrNotCronOperation": {
+			reason: "Should return error if object is not a cron operation.",
+			obj:    v1beta1crd,
+			err:    errors.New(errNotCronOperation),
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			err := IsCronOperation(tc.obj)
+			if diff := cmp.Diff(tc.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nIsCronOperation(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestIsWatchOperation(t *testing.T) {
+	cases := map[string]struct {
+		reason string
+		obj    runtime.Object
+		err    error
+	}{
+		"v1": {
+			reason: "Should not return error if object is a watch operation.",
+			obj:    v1alpha1WatchOp,
+		},
+		"ErrNotWatchOperation": {
+			reason: "Should return error if object is not a watch operation.",
+			obj:    v1beta1crd,
+			err:    errors.New(errNotWatchOperation),
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			err := IsWatchOperation(tc.obj)
+			if diff := cmp.Diff(tc.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nIsWatchOperation(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
diff --git a/internal/xpkg/testdata/provider_meta.yaml b/internal/xpkg/testdata/provider_meta.yaml
index 3e8bad738..51033764e 100755
--- a/internal/xpkg/testdata/provider_meta.yaml
+++ b/internal/xpkg/testdata/provider_meta.yaml
@@ -135,13 +135,13 @@ metadata:
     friendly-name.meta.crossplane.io: Provider AWS
     iconData: CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNjUiIGhlaWdodD0iNjUiPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiB4PSIuNSIgeT0iLjUiIGZpbGw9IiNGQUZBRkEiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjRDhEOERBIiByeD0iMTYiLz48cGF0aCBmaWxsPSIjMjUyRjNFIiBkPSJNMjMuMjQ2MzY1NiAyOS45Njg5OTdjMCAuNTY0MTg0NC4wNTY0MjQ2IDEuMDIxNjMxMi4xNTUxNjc2IDEuMzU3MDkyMi4xMTI4NDkyLjMzNTQ2MS4yNTM5MTA3LjcwMTQxODQuNDUxMzk2OCAxLjA5Nzg3MjMuMDcwNTMwOC4xMjE5ODU4LjA5ODc0MzEuMjQzOTcxNi4wOTg3NDMxLjM1MDcwOTIgMCAuMTUyNDgyMy0uMDg0NjM2OS4zMDQ5NjQ2LS4yNjgwMTY5LjQ1NzQ0NjhsLS44ODg2ODc1LjY0MDQyNTZjLS4xMjY5NTUzLjA5MTQ4OTMtLjI1NDA1MTcuMTM3MjM0LS4zNjY3NTk5LjEzNzIzNC0uMTQxMDYxNSAwLS4yODIxMjMtLjA3NjI0MTEtLjQyMzE4NDUtLjIxMzQ3NTItLjE5NzQ4NjEtLjIyODcyMzQtLjM2Njc1OTktLjQ3MjY5NS0uNTA3ODIxNC0uNzE2NjY2Ni0uMTQxMDYxNS0uMjU5MjE5OS0uMjgyMTIzLS41NDg5MzYyLS40MzcyOTA2LS44OTk2NDU0LTEuMTAwNDIwNzggMS40MDI4MzY4LTIuNDgyNjgyNDIgMi4xMDQyNTUzLTQuMTQ3MjA4MTMgMi4xMDQyNTUzLTEuMTg0OTE2NjEgMC0yLjEzMDAyODY2LS4zNjU5NTc1LTIuODIxMjMwMDItMS4wOTc4NzI0LS42OTEyMDEzNS0uNzMxOTE0OC0xLjA0Mzg1NTEtMS43MDc4MDE0LTEuMDQzODU1MS0yLjkyNzY1OTUgMC0xLjI5NjA5OTMyLjQyMzA0MzQ0LTIuMzQ4MjI2OTggMS4yODM2NTk2NS0zLjE0MTEzNDc4Ljg2MDQ3NTE2LS43OTI5MDc4IDIuMDAzMDczMzEtMS4xODkzNjE3IDMuNDU2MDA2NzctMS4xODkzNjE3LjQ3OTQ2ODA0IDAgLjk3MzMyNDM2LjA0NTc0NDY4IDEuNDk1MjUxOTEuMTIxOTg1ODEuNTIxOTI3NTUuMDc2MjQxMTQgMS4wNTc5NjEyNi4xOTgyMjY5NSAxLjYyMjIwNzI2LjMzNTQ2MXYtMS4xMTMxMjA1N2MwLTEuMTU4ODY1MjUtLjIyNTY5ODQtMS45NjcwMjEyOC0uNjYyOTg5MDUtMi40Mzk3MTYzMS0uNDUxMzk2ODEtLjQ3MjY5NTA0LTEuMjEzMTI4OTEtLjcwMTQxODQ0LTIuMjk5MzAyNDctLjcwMTQxODQ0LS40OTM3MTUyNSAwLTEuMDAxNTM2NjUuMDYwOTkyOTEtMS41MjM0NjQyLjE5ODIyNjk1LS41MjE5Mjc1Ni4xMzcyMzQwNC0xLjAyOTc0ODk2LjMwNDk2NDU0LTEuNTIzNDY0MjEuNTE4NDM5NzEtLjIyNTY5ODQuMTA2NzM3NTktLjM5NDk3MjIuMTY3NzMwNS0uNDkzNzE1MjYuMTk4MjI2OTYtLjA5ODc0MzA1LjAzMDQ5NjQ1LS4xNjkyNzM4LjA0NTc0NDY4LS4yMjU4Mzk0Ni4wNDU3NDQ2OC0uMTk3MzQ1MDQgMC0uMjk2MDg4MDktLjE1MjQ4MjI3LS4yOTYwODgwOS0uNDcyNjk1MDR2LS43NDcxNjMxMmMwLS4yNDM5NzE2My4wMjgyMTIzLS40MjY5NTAzNS4wOTg3NDMwNS0uNTMzNjg3OTRzLjE5NzM0NTA0LS4yMTM0NzUxOC4zOTQ5NzIyLS4zMjAyMTI3N2MuNDkzNTc0Mi0uMjc0NDY4MDggMS4wODYxNzM1Ni0uNTAzMTkxNDkgMS43NzczNzQ5MS0uNjg2MTcwMjEuNjkxMjAxMzYtLjE5ODIyNjk1IDEuNDI0NzIxMTYtLjI4OTcxNjMxIDIuMjAwNTU5NDItLjI4OTcxNjMxIDEuNjc4NjMxODUgMCAyLjkwNTg2NjkyLjQxMTcwMjEyIDMuNjk1ODExMzIgMS4yMzUxMDYzOC43NzU4MzgyLjgyMzQwNDI1IDEuMTcwODEwNSAyLjA3Mzc1ODg2IDEuMTcwODEwNSAzLjc1MTA2MzgzVjI5Ljk2ODk5N2guMDI4MjEyM3ptLTUuNzI3MDk2OTggMi4zMTc3MzA1Yy40NjU1MDI5NSAwIC45NDQ5NzA5OS0uMDkxNDg5NCAxLjQ1MjkzMzQ2LS4yNzQ0NjgxLjUwNzgyMTQtLjE4Mjk3ODguOTU5MjE4Mi0uNTE4NDM5NyAxLjM0MDA4NDI2LS45NzU4ODY2LjIyNTY5ODQtLjI4OTcxNjMuMzk0ODMxMTQtLjYwOTkyOS40Nzk2MDkxLS45NzU4ODY1LjA4NDYzNjktLjM2NTk1NzQuMTQxMDYxNS0uODA4MTU2MDEuMTQxMDYxNS0xLjMyNjU5NTcydi0uNjQwNDI1NTNjLS40MDkwNzgzNS0uMTA2NzM3NTktLjg0NjM2OTAxLS4xOTgyMjY5NS0xLjI5Nzc2NTgxLS4yNTkyMTk4Ni0uNDUxMzk2OC0uMDYwOTkyOTEtLjg4ODY4NzQ1LS4wOTE0ODkzNi0xLjMyNTk3ODEtLjA5MTQ4OTM2LS45NDUxMTIwNiAwLTEuNjM2MzEzNDEuMTk4MjI2OTUtMi4xMDE4MTYzNy42MDk5MjkwNy0uNDY1NTAyOTUuNDExNzAyMTMtLjY5MTIwMTM1Ljk5MTEzNDc2LS42OTEyMDEzNSAxLjc1MzU0NjEgMCAuNzE2NjY2Ny4xNjkyNzM4IDEuMjUwMzU0Ni41MjE5Mjc1NSAxLjYxNjMxMjEuMzM4NTQ3NjEuMzgxMjA1Ni44MzIyNjI4Ni41NjQxODQ0IDEuNDgxMTQ1NzYuNTY0MTg0NHpNMjguODQ2NTA3MSAzMy45MzM1MzZjLS4yNTM5MTA3IDAtLjQyMzE4NDUtLjA0NTc0NDctLjUzNjAzMzctLjE1MjQ4MjMtLjExMjg0OTItLjA5MTQ4OTQtLjIxMTU5MjItLjMwNDk2NDUtLjI5NjIyOTEtLjU5NDY4MDlMMjQuNjk5Mjk5IDIxLjM5OTQ5MzQxYy0uMDg0Nzc4LS4zMDQ5NjQ1My0uMTI2OTU1My0uNTAzMTkxNDgtLjEyNjk1NTMtLjYwOTkyOTA3IDAtLjI0Mzk3MTYzLjExMjcwODEtLjM4MTIwNTY4LjMzODU0NzYtLjM4MTIwNTY4aDEuMzgyNDAyN2MuMjY3ODc1OCAwIC40NTEzOTY4LjA0NTc0NDY4LjU1MDEzOTguMTUyNDgyMjcuMTEyODQ5Mi4wOTE0ODkzNi4xOTc0ODYxLjMwNDk2NDU0LjI4MjEyMy41OTQ2ODA4NUwyOS40OTUzOSAzMS4yNDk4NDhsMi4yMDA0MTg0LTEwLjA5NDMyNjIyYy4wNzA2NzE4LS4zMDQ5NjQ1NC4xNTUxNjc2LS41MDMxOTE0OS4yNjgxNTc5LS41OTQ2ODA4NS4xMTI4NDkyLS4wOTE0ODkzNi4zMTAxOTQyLS4xNTI0ODIyNy41NjQyNDYtLjE1MjQ4MjI3aDEuMTI4NDkyYy4yNjc4NzU4IDAgLjQ1MTM5NjguMDQ1NzQ0NjguNTY0MjQ2LjE1MjQ4MjI3LjExMjg0OTIuMDkxNDg5MzYuMjExNTkyMy4zMDQ5NjQ1NC4yNjgwMTY5LjU5NDY4MDg1bDIuMjI4NjMwNiAxMC4yMTYzMTIwMiAyLjQ0MDUwNS0xMC4yMTYzMTIwMmMuMDg0NjM2OS0uMzA0OTY0NTQuMTgzMzgtLjUwMzE5MTQ5LjI4MjEyMy0uNTk0NjgwODUuMTEyODQ5Mi0uMDkxNDg5MzYuMjk2MjI5Mi0uMTUyNDgyMjcuNTUwMTM5OS0uMTUyNDgyMjdoMS4zMTE4NzE5Yy4yMjU2OTg0IDAgLjM1MjY1MzguMTIxOTg1ODIuMzUyNjUzOC4zODEyMDU2OCAwIC4wNzYyNDExMy0uMDE0MTA2Mi4xNTI0ODIyNy0uMDI4MjEyMy4yNDM5NzE2My0uMDE0MjQ3Mi4wOTE0ODkzNi0uMDQyMzE4NS4yMTM0NzUxNy0uMDk4NzQzMS4zODEyMDU2N0wzOC4xMjgzNTM5IDMzLjIwMTYyMTFjLS4wODQ3NzguMzA0OTY0NS0uMTgzNTIxLjUwMzE5MTUtLjI5NjM3MDIuNTk0NjgwOC0uMTEyNzA4Mi4wOTE0ODk0LS4yOTYwODgxLjE1MjQ4MjMtLjUzNTg5MjcuMTUyNDgyM2gtMS4yMTMyNjk5Yy0uMjY3ODc1OCAwLS40NTEyNTU4LS4wNDU3NDQ3LS41NjQyNDYtLjE1MjQ4MjMtLjExMjg0OTItLjEwNjczNzYtLjIxMTU5MjMtLjMwNDk2NDUtLjI2Nzg3NTgtLjYwOTkyOTFsLTIuMTg2NDUzMy05LjgzNTEwNjM0LTIuMTcyMzQ3MSA5LjgxOTg1ODE0Yy0uMDcwNjcxOC4zMDQ5NjQ2LS4xNTUxNjc3LjUwMzE5MTUtLjI2ODAxNjkuNjA5OTI5MS0uMTEyODQ5Mi4xMDY3Mzc2LS4zMTAzMzUzLjE1MjQ4MjMtLjU2NDI0Ni4xNTI0ODIzaC0xLjIxMzEyODl6bTE4LjEyNjQwMjkuNDExNzAyMWMtLjczMzUxOTggMC0xLjQ2NzAzOTYtLjA5MTQ4OTQtMi4xNzIzNDcxLS4yNzQ0Njgxcy0xLjI1NTQ0NzQtLjM4MTIwNTctMS42MjIyMDczLS42MDk5MjkxYy0uMjI1Njk4NC0uMTM3MjM0LS4zODEwMDcxLS4yODk3MTYzLS40MzcyOTA2LS40MjY5NTAzLS4wNTY0MjQ2LS4xMzcyMzQxLS4wODQ2MzY5LS4yODk3MTYzLS4wODQ2MzY5LS40MjY5NTA0di0uNzc3NjU5NmMwLS4zMjAyMTI3LjExMjg0OTItLjQ3MjY5NS4zMjQ0NDE0LS40NzI2OTUuMDg0NjM2OSAwIC4xNjkxMzI3LjAxNTI0ODIuMjUzNzY5Ni4wNDU3NDQ3LjA4NDc3OC4wMzA0OTY0LjIxMTU5MjMuMDkxNDg5NC4zNTI3OTQ5LjE1MjQ4MjMuNDc5NDY4LjIyODcyMzQgMS4wMDE1MzY2LjQxMTcwMjEgMS41NTE1MzU0LjUzMzY4NzkuNTY0Mzg3MS4xMjE5ODU4IDEuMTE0Mzg1OS4xODI5Nzg3IDEuNjc4NzcyOS4xODI5Nzg3Ljg4ODY4NzUgMCAxLjU3OTg4ODgtLjE2NzczMDUgMi4wNTkzNTY5LS41MDMxOTE1LjQ3OTc1MDEtLjMzNTQ2MS43MzM2NjA4LS44MjM0MDQyLjczMzY2MDgtMS40NDg1ODE1IDAtLjQyNjk1MDQtLjEyNjk1NTMtLjc3NzY1OTYtLjM4MTAwNzEtMS4wNjczNzU5MS0uMjUzNzY5Ni0uMjg5NzE2MzEtLjczMzM3ODctLjU0ODkzNjE3LTEuNDI0NzIxMS0uNzkyOTA3OGwtMi4wNDUyNTA3LS42ODYxNzAyMWMtMS4wMjk3NDktLjM1MDcwOTIyLTEuNzkxNDgxMS0uODY5MTQ4OTQtMi4yNTY5ODQtMS41NTUzMTkxNS0uNDY1NTAzLS42NzA5MjE5OS0uNzA1NDQ4Ni0xLjQxODA4NTExLS43MDU0NDg2LTIuMjEwOTkyOTEgMC0uNjQwNDI1NTMuMTI3MDk2NC0xLjIwNDYwOTkzLjM4MTAwNzEtMS42OTI1NTMxOS4yNTM3Njk2LS40ODc5NDMyNi41OTI0NTgzLS45MTQ4OTM2MiAxLjAxNTY0MjgtMS4yNTAzNTQ2MS40MjMxODQ1LS4zNTA3MDkyMi45MDI3OTM2LS42MDk5MjkwOCAxLjQ2NzAzOTYtLjc5MjkwNzguNTY0MjQ2LS4xODI5Nzg3MiAxLjE1NjcwNDMtLjI1OTIxOTg2IDEuNzc3Mzc0OS0uMjU5MjE5ODYuMzEwMTk0MyAwIC42MzQ3NzY4LjAxNTI0ODIzLjk0NDk3MS4wNjA5OTI5MS4zMjQ1ODI1LjA0NTc0NDY4LjYyMDY3MDYuMTA2NzM3NTkuOTE2ODk5OC4xNjc3MzA1LjI4MjEyMy4wNzYyNDExMy41NTAxMzk4LjE1MjQ4MjI3LjgwNDE5MTYuMjQzOTcxNjMuMjUzNzY5Ni4wOTE0ODkzNi40NTEzOTY4LjE4Mjk3ODcyLjU5MjQ1ODMuMjc0NDY4MDguMTk3NDg2MS4xMjE5ODU4Mi4zMzg1NDc2LjI0Mzk3MTYzLjQyMzE4NDUuMzgxMjA1NjguMDg0NjM2OS4xMjE5ODU4MS4xMjY5NTUzLjI4OTcxNjMxLjEyNjk1NTMuNTAzMTkxNDl2LjcxNjY2NjY2YzAgLjMyMDIxMjc3LS4xMTI4NDkyLjQ4Nzk0MzI2LS4zMjQ0NDE0LjQ4Nzk0MzI2LS4xMTI4NDkyIDAtLjI5NjIyOTItLjA2MDk5MjktLjUzNjAzMzctLjE4Mjk3ODcyLS44MDQwNTA2LS4zOTY0NTM5LTEuNzA2ODQ0Mi0uNTk0NjgwODUtMi43MDgzODA4LS41OTQ2ODA4NS0uODA0MDUwNiAwLTEuNDM4ODI3My4xMzcyMzQwNC0xLjg3NjExOC40MjY5NTAzNS0uNDM3MjkwNi4yODk3MTYzMi0uNjYyOTg5LjczMTkxNDktLjY2Mjk4OSAxLjM1NzA5MjIgMCAuNDI2OTUwMzYuMTQxMDYxNS43OTI5MDc4LjQyMzE4NDUgMS4wODI2MjQxMi4yODIxMjMuMjg5NzE2MzEuODA0MDUwNS41Nzk0MzI2MiAxLjU1MTY3NjUuODM4NjUyNDhsMi4wMDMwNzMzLjY4NjE3MDIxYzEuMDE1NjQyOC4zNTA3MDkyMiAxLjc0OTE2MjYuODM4NjUyNDggMi4xODY0NTMyIDEuNDYzODI5NzkuNDM3MjkwNy42MjUxNzczLjY0ODg4MyAxLjM0MTg0Mzk3LjY0ODg4MyAyLjEzNDc1MTc1IDAgLjY1NTY3MzgtLjEyNjk1NTQgMS4yNTAzNTQ2LS4zNjY3NTk5IDEuNzY4Nzk0My0uMjUzOTEwOC41MTg0Mzk4LS41OTI0NTg0Ljk3NTg4NjYtMS4wMjk3NDkgMS4zNDE4NDQtLjQzNzI5MDcuMzgxMjA1Ny0uOTU5MjE4Mi42NTU2NzM4LTEuNTY1NzgyNy44NTM5MDA3LS42MzQ3NzY3LjIxMzQ3NTItMS4yOTc3NjU4LjMyMDIxMjgtMi4wMTcxNzk0LjMyMDIxMjh6Ii8+PHBhdGggZmlsbD0iI0ZGOTkwMCIgZD0iTTQ5Ljg3NTUwNyA0MS4wMDU5MzcyYy00LjcwNTEzNjcgMy42NzQ0MDUxLTExLjU0MTE3MTIgNS42MjUwMTUyLTE3LjQxODg3MzggNS42MjUwMTUyLTguMjM3NTY0NiAwLTE1LjY1OTk1MzYxLTMuMjIwNzc0OC0yMS4yNjYwNzM5OC04LjU3MzYxMTktLjQ0MzQ4NDE2LS40MjMzODgyLS4wNDI5MDM5OC0uOTk3OTg2NS40ODYyNDUxNC0uNjY1MzI0NCA2LjA2Mzc2Mjg1IDMuNzE5NzY4MiAxMy41NDMyMTQxNCA1Ljk3Mjc5ODQgMjEuMjgwMzc1MjQgNS45NzI3OTg0IDUuMjE5OTg0NiAwIDEwLjk1NDgxNjktMS4xNDkxOTY2IDE2LjIzMjAwNjctMy41MDgwNzQuNzg2NTczLS4zNzgwMjUyIDEuNDU4NzM1NC41NDQzNTYzLjY4NjMyMDcgMS4xNDkxOTY3em0xLjk1OTQyNDktMi4zNTg4NzczYy0uNjAwNzk4OC0uODE2NTM0NS0zLjk3NTc2OS0uMzkzMTQ2My01LjUwNjAxMTEtLjE5NjU3MzItLjQ1Nzc4NTUuMDYwNDg0MS0uNTI5MjkyMS0uMzYyOTA0Mi0uMTE0NDEwNi0uNjgwNDQ1MyAyLjY4ODY0OTYtMS45OTU5NzMyIDcuMTA3NjE2Ny0xLjQyMTM3NDggNy42MjI2MDc2LS43NTYwNTA1LjUxNDg0NzcuNjgwNDQ1NC0uMTQzMDEzMyA1LjM1MjgzNzEtMi42NjAxOSA3LjU5MDc0NjMtLjM4NTk5MjguMzQ3NzgzMi0uNzU3ODI3My4xNjYzMzExLS41ODYzNTQ0LS4yODcyOTkxLjU3MjA1MzEtMS40OTY5Nzk5IDEuODQ1MDE0My00Ljg2ODk2NDggMS4yNDQzNTg1LTUuNjcwMzc4MnoiLz48L2c+PC9zdmc+
     license: Apache-2.0
-    maintainer: Crossplane Maintainers <info@crossplane.io>
+    maintainer: Crossplane Maintainers <crossplane-info@lists.cncf.io>
     meta.crossplane.io/description: |
       The Amazon Web Services (AWS) Crossplane provider adds support for
       managing AWS resources in Kubernetes.
     meta.crossplane.io/iconURI: data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNjUiIGhlaWdodD0iNjUiPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiB4PSIuNSIgeT0iLjUiIGZpbGw9IiNGQUZBRkEiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjRDhEOERBIiByeD0iMTYiLz48cGF0aCBmaWxsPSIjMjUyRjNFIiBkPSJNMjMuMjQ2MzY1NiAyOS45Njg5OTdjMCAuNTY0MTg0NC4wNTY0MjQ2IDEuMDIxNjMxMi4xNTUxNjc2IDEuMzU3MDkyMi4xMTI4NDkyLjMzNTQ2MS4yNTM5MTA3LjcwMTQxODQuNDUxMzk2OCAxLjA5Nzg3MjMuMDcwNTMwOC4xMjE5ODU4LjA5ODc0MzEuMjQzOTcxNi4wOTg3NDMxLjM1MDcwOTIgMCAuMTUyNDgyMy0uMDg0NjM2OS4zMDQ5NjQ2LS4yNjgwMTY5LjQ1NzQ0NjhsLS44ODg2ODc1LjY0MDQyNTZjLS4xMjY5NTUzLjA5MTQ4OTMtLjI1NDA1MTcuMTM3MjM0LS4zNjY3NTk5LjEzNzIzNC0uMTQxMDYxNSAwLS4yODIxMjMtLjA3NjI0MTEtLjQyMzE4NDUtLjIxMzQ3NTItLjE5NzQ4NjEtLjIyODcyMzQtLjM2Njc1OTktLjQ3MjY5NS0uNTA3ODIxNC0uNzE2NjY2Ni0uMTQxMDYxNS0uMjU5MjE5OS0uMjgyMTIzLS41NDg5MzYyLS40MzcyOTA2LS44OTk2NDU0LTEuMTAwNDIwNzggMS40MDI4MzY4LTIuNDgyNjgyNDIgMi4xMDQyNTUzLTQuMTQ3MjA4MTMgMi4xMDQyNTUzLTEuMTg0OTE2NjEgMC0yLjEzMDAyODY2LS4zNjU5NTc1LTIuODIxMjMwMDItMS4wOTc4NzI0LS42OTEyMDEzNS0uNzMxOTE0OC0xLjA0Mzg1NTEtMS43MDc4MDE0LTEuMDQzODU1MS0yLjkyNzY1OTUgMC0xLjI5NjA5OTMyLjQyMzA0MzQ0LTIuMzQ4MjI2OTggMS4yODM2NTk2NS0zLjE0MTEzNDc4Ljg2MDQ3NTE2LS43OTI5MDc4IDIuMDAzMDczMzEtMS4xODkzNjE3IDMuNDU2MDA2NzctMS4xODkzNjE3LjQ3OTQ2ODA0IDAgLjk3MzMyNDM2LjA0NTc0NDY4IDEuNDk1MjUxOTEuMTIxOTg1ODEuNTIxOTI3NTUuMDc2MjQxMTQgMS4wNTc5NjEyNi4xOTgyMjY5NSAxLjYyMjIwNzI2LjMzNTQ2MXYtMS4xMTMxMjA1N2MwLTEuMTU4ODY1MjUtLjIyNTY5ODQtMS45NjcwMjEyOC0uNjYyOTg5MDUtMi40Mzk3MTYzMS0uNDUxMzk2ODEtLjQ3MjY5NTA0LTEuMjEzMTI4OTEtLjcwMTQxODQ0LTIuMjk5MzAyNDctLjcwMTQxODQ0LS40OTM3MTUyNSAwLTEuMDAxNTM2NjUuMDYwOTkyOTEtMS41MjM0NjQyLjE5ODIyNjk1LS41MjE5Mjc1Ni4xMzcyMzQwNC0xLjAyOTc0ODk2LjMwNDk2NDU0LTEuNTIzNDY0MjEuNTE4NDM5NzEtLjIyNTY5ODQuMTA2NzM3NTktLjM5NDk3MjIuMTY3NzMwNS0uNDkzNzE1MjYuMTk4MjI2OTYtLjA5ODc0MzA1LjAzMDQ5NjQ1LS4xNjkyNzM4LjA0NTc0NDY4LS4yMjU4Mzk0Ni4wNDU3NDQ2OC0uMTk3MzQ1MDQgMC0uMjk2MDg4MDktLjE1MjQ4MjI3LS4yOTYwODgwOS0uNDcyNjk1MDR2LS43NDcxNjMxMmMwLS4yNDM5NzE2My4wMjgyMTIzLS40MjY5NTAzNS4wOTg3NDMwNS0uNTMzNjg3OTRzLjE5NzM0NTA0LS4yMTM0NzUxOC4zOTQ5NzIyLS4zMjAyMTI3N2MuNDkzNTc0Mi0uMjc0NDY4MDggMS4wODYxNzM1Ni0uNTAzMTkxNDkgMS43NzczNzQ5MS0uNjg2MTcwMjEuNjkxMjAxMzYtLjE5ODIyNjk1IDEuNDI0NzIxMTYtLjI4OTcxNjMxIDIuMjAwNTU5NDItLjI4OTcxNjMxIDEuNjc4NjMxODUgMCAyLjkwNTg2NjkyLjQxMTcwMjEyIDMuNjk1ODExMzIgMS4yMzUxMDYzOC43NzU4MzgyLjgyMzQwNDI1IDEuMTcwODEwNSAyLjA3Mzc1ODg2IDEuMTcwODEwNSAzLjc1MTA2MzgzVjI5Ljk2ODk5N2guMDI4MjEyM3ptLTUuNzI3MDk2OTggMi4zMTc3MzA1Yy40NjU1MDI5NSAwIC45NDQ5NzA5OS0uMDkxNDg5NCAxLjQ1MjkzMzQ2LS4yNzQ0NjgxLjUwNzgyMTQtLjE4Mjk3ODguOTU5MjE4Mi0uNTE4NDM5NyAxLjM0MDA4NDI2LS45NzU4ODY2LjIyNTY5ODQtLjI4OTcxNjMuMzk0ODMxMTQtLjYwOTkyOS40Nzk2MDkxLS45NzU4ODY1LjA4NDYzNjktLjM2NTk1NzQuMTQxMDYxNS0uODA4MTU2MDEuMTQxMDYxNS0xLjMyNjU5NTcydi0uNjQwNDI1NTNjLS40MDkwNzgzNS0uMTA2NzM3NTktLjg0NjM2OTAxLS4xOTgyMjY5NS0xLjI5Nzc2NTgxLS4yNTkyMTk4Ni0uNDUxMzk2OC0uMDYwOTkyOTEtLjg4ODY4NzQ1LS4wOTE0ODkzNi0xLjMyNTk3ODEtLjA5MTQ4OTM2LS45NDUxMTIwNiAwLTEuNjM2MzEzNDEuMTk4MjI2OTUtMi4xMDE4MTYzNy42MDk5MjkwNy0uNDY1NTAyOTUuNDExNzAyMTMtLjY5MTIwMTM1Ljk5MTEzNDc2LS42OTEyMDEzNSAxLjc1MzU0NjEgMCAuNzE2NjY2Ny4xNjkyNzM4IDEuMjUwMzU0Ni41MjE5Mjc1NSAxLjYxNjMxMjEuMzM4NTQ3NjEuMzgxMjA1Ni44MzIyNjI4Ni41NjQxODQ0IDEuNDgxMTQ1NzYuNTY0MTg0NHpNMjguODQ2NTA3MSAzMy45MzM1MzZjLS4yNTM5MTA3IDAtLjQyMzE4NDUtLjA0NTc0NDctLjUzNjAzMzctLjE1MjQ4MjMtLjExMjg0OTItLjA5MTQ4OTQtLjIxMTU5MjItLjMwNDk2NDUtLjI5NjIyOTEtLjU5NDY4MDlMMjQuNjk5Mjk5IDIxLjM5OTQ5MzQxYy0uMDg0Nzc4LS4zMDQ5NjQ1My0uMTI2OTU1My0uNTAzMTkxNDgtLjEyNjk1NTMtLjYwOTkyOTA3IDAtLjI0Mzk3MTYzLjExMjcwODEtLjM4MTIwNTY4LjMzODU0NzYtLjM4MTIwNTY4aDEuMzgyNDAyN2MuMjY3ODc1OCAwIC40NTEzOTY4LjA0NTc0NDY4LjU1MDEzOTguMTUyNDgyMjcuMTEyODQ5Mi4wOTE0ODkzNi4xOTc0ODYxLjMwNDk2NDU0LjI4MjEyMy41OTQ2ODA4NUwyOS40OTUzOSAzMS4yNDk4NDhsMi4yMDA0MTg0LTEwLjA5NDMyNjIyYy4wNzA2NzE4LS4zMDQ5NjQ1NC4xNTUxNjc2LS41MDMxOTE0OS4yNjgxNTc5LS41OTQ2ODA4NS4xMTI4NDkyLS4wOTE0ODkzNi4zMTAxOTQyLS4xNTI0ODIyNy41NjQyNDYtLjE1MjQ4MjI3aDEuMTI4NDkyYy4yNjc4NzU4IDAgLjQ1MTM5NjguMDQ1NzQ0NjguNTY0MjQ2LjE1MjQ4MjI3LjExMjg0OTIuMDkxNDg5MzYuMjExNTkyMy4zMDQ5NjQ1NC4yNjgwMTY5LjU5NDY4MDg1bDIuMjI4NjMwNiAxMC4yMTYzMTIwMiAyLjQ0MDUwNS0xMC4yMTYzMTIwMmMuMDg0NjM2OS0uMzA0OTY0NTQuMTgzMzgtLjUwMzE5MTQ5LjI4MjEyMy0uNTk0NjgwODUuMTEyODQ5Mi0uMDkxNDg5MzYuMjk2MjI5Mi0uMTUyNDgyMjcuNTUwMTM5OS0uMTUyNDgyMjdoMS4zMTE4NzE5Yy4yMjU2OTg0IDAgLjM1MjY1MzguMTIxOTg1ODIuMzUyNjUzOC4zODEyMDU2OCAwIC4wNzYyNDExMy0uMDE0MTA2Mi4xNTI0ODIyNy0uMDI4MjEyMy4yNDM5NzE2My0uMDE0MjQ3Mi4wOTE0ODkzNi0uMDQyMzE4NS4yMTM0NzUxNy0uMDk4NzQzMS4zODEyMDU2N0wzOC4xMjgzNTM5IDMzLjIwMTYyMTFjLS4wODQ3NzguMzA0OTY0NS0uMTgzNTIxLjUwMzE5MTUtLjI5NjM3MDIuNTk0NjgwOC0uMTEyNzA4Mi4wOTE0ODk0LS4yOTYwODgxLjE1MjQ4MjMtLjUzNTg5MjcuMTUyNDgyM2gtMS4yMTMyNjk5Yy0uMjY3ODc1OCAwLS40NTEyNTU4LS4wNDU3NDQ3LS41NjQyNDYtLjE1MjQ4MjMtLjExMjg0OTItLjEwNjczNzYtLjIxMTU5MjMtLjMwNDk2NDUtLjI2Nzg3NTgtLjYwOTkyOTFsLTIuMTg2NDUzMy05LjgzNTEwNjM0LTIuMTcyMzQ3MSA5LjgxOTg1ODE0Yy0uMDcwNjcxOC4zMDQ5NjQ2LS4xNTUxNjc3LjUwMzE5MTUtLjI2ODAxNjkuNjA5OTI5MS0uMTEyODQ5Mi4xMDY3Mzc2LS4zMTAzMzUzLjE1MjQ4MjMtLjU2NDI0Ni4xNTI0ODIzaC0xLjIxMzEyODl6bTE4LjEyNjQwMjkuNDExNzAyMWMtLjczMzUxOTggMC0xLjQ2NzAzOTYtLjA5MTQ4OTQtMi4xNzIzNDcxLS4yNzQ0Njgxcy0xLjI1NTQ0NzQtLjM4MTIwNTctMS42MjIyMDczLS42MDk5MjkxYy0uMjI1Njk4NC0uMTM3MjM0LS4zODEwMDcxLS4yODk3MTYzLS40MzcyOTA2LS40MjY5NTAzLS4wNTY0MjQ2LS4xMzcyMzQxLS4wODQ2MzY5LS4yODk3MTYzLS4wODQ2MzY5LS40MjY5NTA0di0uNzc3NjU5NmMwLS4zMjAyMTI3LjExMjg0OTItLjQ3MjY5NS4zMjQ0NDE0LS40NzI2OTUuMDg0NjM2OSAwIC4xNjkxMzI3LjAxNTI0ODIuMjUzNzY5Ni4wNDU3NDQ3LjA4NDc3OC4wMzA0OTY0LjIxMTU5MjMuMDkxNDg5NC4zNTI3OTQ5LjE1MjQ4MjMuNDc5NDY4LjIyODcyMzQgMS4wMDE1MzY2LjQxMTcwMjEgMS41NTE1MzU0LjUzMzY4NzkuNTY0Mzg3MS4xMjE5ODU4IDEuMTE0Mzg1OS4xODI5Nzg3IDEuNjc4NzcyOS4xODI5Nzg3Ljg4ODY4NzUgMCAxLjU3OTg4ODgtLjE2NzczMDUgMi4wNTkzNTY5LS41MDMxOTE1LjQ3OTc1MDEtLjMzNTQ2MS43MzM2NjA4LS44MjM0MDQyLjczMzY2MDgtMS40NDg1ODE1IDAtLjQyNjk1MDQtLjEyNjk1NTMtLjc3NzY1OTYtLjM4MTAwNzEtMS4wNjczNzU5MS0uMjUzNzY5Ni0uMjg5NzE2MzEtLjczMzM3ODctLjU0ODkzNjE3LTEuNDI0NzIxMS0uNzkyOTA3OGwtMi4wNDUyNTA3LS42ODYxNzAyMWMtMS4wMjk3NDktLjM1MDcwOTIyLTEuNzkxNDgxMS0uODY5MTQ4OTQtMi4yNTY5ODQtMS41NTUzMTkxNS0uNDY1NTAzLS42NzA5MjE5OS0uNzA1NDQ4Ni0xLjQxODA4NTExLS43MDU0NDg2LTIuMjEwOTkyOTEgMC0uNjQwNDI1NTMuMTI3MDk2NC0xLjIwNDYwOTkzLjM4MTAwNzEtMS42OTI1NTMxOS4yNTM3Njk2LS40ODc5NDMyNi41OTI0NTgzLS45MTQ4OTM2MiAxLjAxNTY0MjgtMS4yNTAzNTQ2MS40MjMxODQ1LS4zNTA3MDkyMi45MDI3OTM2LS42MDk5MjkwOCAxLjQ2NzAzOTYtLjc5MjkwNzguNTY0MjQ2LS4xODI5Nzg3MiAxLjE1NjcwNDMtLjI1OTIxOTg2IDEuNzc3Mzc0OS0uMjU5MjE5ODYuMzEwMTk0MyAwIC42MzQ3NzY4LjAxNTI0ODIzLjk0NDk3MS4wNjA5OTI5MS4zMjQ1ODI1LjA0NTc0NDY4LjYyMDY3MDYuMTA2NzM3NTkuOTE2ODk5OC4xNjc3MzA1LjI4MjEyMy4wNzYyNDExMy41NTAxMzk4LjE1MjQ4MjI3LjgwNDE5MTYuMjQzOTcxNjMuMjUzNzY5Ni4wOTE0ODkzNi40NTEzOTY4LjE4Mjk3ODcyLjU5MjQ1ODMuMjc0NDY4MDguMTk3NDg2MS4xMjE5ODU4Mi4zMzg1NDc2LjI0Mzk3MTYzLjQyMzE4NDUuMzgxMjA1NjguMDg0NjM2OS4xMjE5ODU4MS4xMjY5NTUzLjI4OTcxNjMxLjEyNjk1NTMuNTAzMTkxNDl2LjcxNjY2NjY2YzAgLjMyMDIxMjc3LS4xMTI4NDkyLjQ4Nzk0MzI2LS4zMjQ0NDE0LjQ4Nzk0MzI2LS4xMTI4NDkyIDAtLjI5NjIyOTItLjA2MDk5MjktLjUzNjAzMzctLjE4Mjk3ODcyLS44MDQwNTA2LS4zOTY0NTM5LTEuNzA2ODQ0Mi0uNTk0NjgwODUtMi43MDgzODA4LS41OTQ2ODA4NS0uODA0MDUwNiAwLTEuNDM4ODI3My4xMzcyMzQwNC0xLjg3NjExOC40MjY5NTAzNS0uNDM3MjkwNi4yODk3MTYzMi0uNjYyOTg5LjczMTkxNDktLjY2Mjk4OSAxLjM1NzA5MjIgMCAuNDI2OTUwMzYuMTQxMDYxNS43OTI5MDc4LjQyMzE4NDUgMS4wODI2MjQxMi4yODIxMjMuMjg5NzE2MzEuODA0MDUwNS41Nzk0MzI2MiAxLjU1MTY3NjUuODM4NjUyNDhsMi4wMDMwNzMzLjY4NjE3MDIxYzEuMDE1NjQyOC4zNTA3MDkyMiAxLjc0OTE2MjYuODM4NjUyNDggMi4xODY0NTMyIDEuNDYzODI5NzkuNDM3MjkwNy42MjUxNzczLjY0ODg4MyAxLjM0MTg0Mzk3LjY0ODg4MyAyLjEzNDc1MTc1IDAgLjY1NTY3MzgtLjEyNjk1NTQgMS4yNTAzNTQ2LS4zNjY3NTk5IDEuNzY4Nzk0My0uMjUzOTEwOC41MTg0Mzk4LS41OTI0NTg0Ljk3NTg4NjYtMS4wMjk3NDkgMS4zNDE4NDQtLjQzNzI5MDcuMzgxMjA1Ny0uOTU5MjE4Mi42NTU2NzM4LTEuNTY1NzgyNy44NTM5MDA3LS42MzQ3NzY3LjIxMzQ3NTItMS4yOTc3NjU4LjMyMDIxMjgtMi4wMTcxNzk0LjMyMDIxMjh6Ii8+PHBhdGggZmlsbD0iI0ZGOTkwMCIgZD0iTTQ5Ljg3NTUwNyA0MS4wMDU5MzcyYy00LjcwNTEzNjcgMy42NzQ0MDUxLTExLjU0MTE3MTIgNS42MjUwMTUyLTE3LjQxODg3MzggNS42MjUwMTUyLTguMjM3NTY0NiAwLTE1LjY1OTk1MzYxLTMuMjIwNzc0OC0yMS4yNjYwNzM5OC04LjU3MzYxMTktLjQ0MzQ4NDE2LS40MjMzODgyLS4wNDI5MDM5OC0uOTk3OTg2NS40ODYyNDUxNC0uNjY1MzI0NCA2LjA2Mzc2Mjg1IDMuNzE5NzY4MiAxMy41NDMyMTQxNCA1Ljk3Mjc5ODQgMjEuMjgwMzc1MjQgNS45NzI3OTg0IDUuMjE5OTg0NiAwIDEwLjk1NDgxNjktMS4xNDkxOTY2IDE2LjIzMjAwNjctMy41MDgwNzQuNzg2NTczLS4zNzgwMjUyIDEuNDU4NzM1NC41NDQzNTYzLjY4NjMyMDcgMS4xNDkxOTY3em0xLjk1OTQyNDktMi4zNTg4NzczYy0uNjAwNzk4OC0uODE2NTM0NS0zLjk3NTc2OS0uMzkzMTQ2My01LjUwNjAxMTEtLjE5NjU3MzItLjQ1Nzc4NTUuMDYwNDg0MS0uNTI5MjkyMS0uMzYyOTA0Mi0uMTE0NDEwNi0uNjgwNDQ1MyAyLjY4ODY0OTYtMS45OTU5NzMyIDcuMTA3NjE2Ny0xLjQyMTM3NDggNy42MjI2MDc2LS43NTYwNTA1LjUxNDg0NzcuNjgwNDQ1NC0uMTQzMDEzMyA1LjM1MjgzNzEtMi42NjAxOSA3LjU5MDc0NjMtLjM4NTk5MjguMzQ3NzgzMi0uNzU3ODI3My4xNjYzMzExLS41ODYzNTQ0LS4yODcyOTkxLjU3MjA1MzEtMS40OTY5Nzk5IDEuODQ1MDE0My00Ljg2ODk2NDggMS4yNDQzNTg1LTUuNjcwMzc4MnoiLz48L2c+PC9zdmc+
     meta.crossplane.io/license: Apache-2.0
-    meta.crossplane.io/maintainer: Crossplane Maintainers <info@crossplane.io>
+    meta.crossplane.io/maintainer: Crossplane Maintainers <crossplane-info@lists.cncf.io>
     meta.crossplane.io/readme: |
       `provider-aws` is the Crossplane infrastructure provider for [Amazon Web
       Services (AWS)](https://aws.amazon.com/).
diff --git a/test/e2e/apiextensions_activation_policy_test.go b/test/e2e/apiextensions_activation_policy_test.go
new file mode 100644
index 000000000..2190234a4
--- /dev/null
+++ b/test/e2e/apiextensions_activation_policy_test.go
@@ -0,0 +1,265 @@
+/*
+Copyright 2025 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 e2e
+
+import (
+	"testing"
+	"time"
+
+	"sigs.k8s.io/e2e-framework/pkg/features"
+	"sigs.k8s.io/e2e-framework/third_party/helm"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+	"github.com/crossplane/crossplane/test/e2e/config"
+	"github.com/crossplane/crossplane/test/e2e/funcs"
+)
+
+// LabelAreaMRAP is applied to all features pertaining to MRAP testing.
+const LabelAreaMRAP = "mrap"
+
+// Tests that should be part of the test suite for the MRAP feature.
+const SuiteMRAP = "mrap"
+
+func init() {
+	environment.AddTestSuite(SuiteMRAP,
+		config.WithHelmInstallOpts(
+			helm.WithArgs("--set provider.defaultActivations=null"),
+		),
+		config.WithLabelsToSelect(features.Labels{
+			config.LabelTestSuite: []string{SuiteMRAP, config.TestSuiteDefault},
+		}),
+	)
+}
+
+func TestMRAPActivatesSingleMRD(t *testing.T) {
+	manifests := "test/e2e/manifests/apiextensions/activation-policy/single-activation"
+
+	environment.Test(t,
+		features.NewWithDescription(t.Name(),
+			"Tests that ManagedResourceActivationPolicy can activate a single ManagedResourceDefinition.").
+			WithLabel(LabelArea, LabelAreaMRAP).
+			WithLabel(LabelStage, LabelStageBeta).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
+
+			// Setup Phase: Create MRD in Inactive state and disabled MRAP
+			WithSetup("CreateInactiveMRDAndDisabledMRAP", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrd.yaml",
+					v2alpha1.InactiveManaged()),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrap-disabled.yaml",
+					v2alpha1.Healthy()),
+			)).
+
+			// Verify MRD is inactive (no need to check CRD as it should never be created)
+			Assess("MRDIsInactive", funcs.AllOf(
+				funcs.ResourcesHaveFieldValueWithin(10*time.Second, manifests, "setup/mrd.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionInactive)),
+			)).
+
+			// Update MRAP to activate the MRD
+			Assess("UpdateMRAPToActivateMRD", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrap.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrap.yaml",
+					v2alpha1.Healthy()),
+			)).
+
+			// Verify MRD becomes active
+			Assess("MRDBecomesActive", funcs.AllOf(
+				funcs.ResourcesHaveFieldValueWithin(1*time.Minute, manifests, "setup/mrd.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrd.yaml",
+					v2alpha1.EstablishedManaged()),
+			)).
+
+			// Verify CRD is created
+			Assess("CRDIsCreated", funcs.AllOf(
+				funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "expected-crd.yaml"),
+				funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "expected-crd.yaml",
+					funcs.CRDInitialNamesAccepted()),
+			)).
+
+			// Verify MRAP status shows activated MRD
+			Assess("MRAPStatusShowsActivatedMRD",
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "mrap.yaml",
+					"status.activated", []any{"buckets.single.activation-e2e.crossplane.io"}),
+			).
+
+			// Cleanup
+			WithTeardown("DeleteMRD", funcs.AllOf(
+				funcs.DeleteResources(manifests, "setup/*.yaml"),
+				funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}
+
+func TestMRAPWildcardActivation(t *testing.T) {
+	manifests := "test/e2e/manifests/apiextensions/activation-policy/wildcard-activation"
+
+	environment.Test(t,
+		features.NewWithDescription(t.Name(),
+			"Tests that ManagedResourceActivationPolicy can activate multiple ManagedResourceDefinitions using wildcard patterns.").
+			WithLabel(LabelArea, LabelAreaMRAP).
+			WithLabel(LabelStage, LabelStageBeta).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
+
+			// Setup Phase: Create multiple MRDs in Inactive state
+			WithSetup("CreateMultipleInactiveMRDs", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrd-bucket.yaml",
+					v2alpha1.InactiveManaged()),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrd-instance.yaml",
+					v2alpha1.InactiveManaged()),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrd-database.yaml",
+					v2alpha1.InactiveManaged()),
+			)).
+
+			// Verify all MRDs are inactive
+			Assess("AllMRDsAreInactive", funcs.AllOf(
+				funcs.ResourcesHaveFieldValueWithin(10*time.Second, manifests, "setup/mrd-bucket.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionInactive)),
+				funcs.ResourcesHaveFieldValueWithin(10*time.Second, manifests, "setup/mrd-instance.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionInactive)),
+				funcs.ResourcesHaveFieldValueWithin(10*time.Second, manifests, "setup/mrd-database.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionInactive)),
+			)).
+
+			// Create MRAP with wildcard pattern
+			Assess("CreateWildcardMRAPToActivateMRDs", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrap-wildcard.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "mrap-wildcard.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrap-wildcard.yaml",
+					v2alpha1.Healthy()),
+			)).
+
+			// Verify all matching MRDs become active
+			Assess("AllMatchingMRDsBecomeActive", funcs.AllOf(
+				funcs.ResourcesHaveFieldValueWithin(1*time.Minute, manifests, "setup/mrd-bucket.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+				funcs.ResourcesHaveFieldValueWithin(1*time.Minute, manifests, "setup/mrd-instance.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+				funcs.ResourcesHaveFieldValueWithin(1*time.Minute, manifests, "setup/mrd-database.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+			)).
+
+			// Verify all corresponding CRDs are created
+			Assess("AllCRDsAreCreated", funcs.AllOf(
+				funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "expected-crds/bucket-crd.yaml"),
+				funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "expected-crds/instance-crd.yaml"),
+				funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "expected-crds/database-crd.yaml"),
+			)).
+
+			// Verify MRAP status shows all activated MRDs
+			Assess("WildcardMRAPStatusShowsAllActivatedMRDs",
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "mrap-wildcard.yaml",
+					"status.activated", []any{
+						"buckets.wildcard.activation-e2e.crossplane.io",
+						"databases.wildcard.activation-e2e.crossplane.io",
+						"instances.wildcard.activation-e2e.crossplane.io",
+					}),
+			).
+
+			// Cleanup
+			WithTeardown("DeleteWildcardMRAPAndMRDs", funcs.AllOf(
+				funcs.DeleteResources(manifests, "mrap-wildcard.yaml"),
+				funcs.ResourcesDeletedWithin(30*time.Second, manifests, "mrap-wildcard.yaml"),
+				funcs.DeleteResources(manifests, "setup/*.yaml"),
+				funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}
+
+func TestMultipleMRAPsOneMRD(t *testing.T) {
+	manifests := "test/e2e/manifests/apiextensions/activation-policy/multiple-mraps"
+
+	environment.Test(t,
+		features.NewWithDescription(t.Name(),
+			"Tests that multiple ManagedResourceActivationPolicies can manage the same ManagedResourceDefinition.").
+			WithLabel(LabelArea, LabelAreaMRAP).
+			WithLabel(LabelStage, LabelStageBeta).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
+
+			// Setup Phase: Create MRD in Inactive state
+			WithSetup("CreateMultiPolicyTestMRD", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "setup/mrd.yaml",
+					v2alpha1.InactiveManaged()),
+			)).
+
+			// Create first MRAP with specific name
+			Assess("CreateSpecificMRAP", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrap-specific.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "mrap-specific.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrap-specific.yaml",
+					v2alpha1.Healthy()),
+			)).
+
+			// Verify MRD becomes active
+			Assess("MRDActivatedBySpecificMRAP", funcs.AllOf(
+				funcs.ResourcesHaveFieldValueWithin(1*time.Minute, manifests, "setup/mrd.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+			)).
+
+			// Create second MRAP with wildcard pattern
+			Assess("CreateWildcardMRAP", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrap-wildcard.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "mrap-wildcard.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrap-wildcard.yaml",
+					v2alpha1.Healthy()),
+			)).
+
+			// Verify both MRAPs show the MRD as activated
+			Assess("BothMRAPsShowMRDAsActivated", funcs.AllOf(
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "mrap-specific.yaml",
+					"status.activated", []any{"storages.multiple.activation-e2e.crossplane.io"}),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "mrap-wildcard.yaml",
+					"status.activated", []any{"storages.multiple.activation-e2e.crossplane.io"}),
+			)).
+
+			// Delete first MRAP, MRD should stay active (still matched by wildcard)
+			Assess("DeleteSpecificMRAPMRDStaysActive", funcs.AllOf(
+				funcs.DeleteResources(manifests, "mrap-specific.yaml"),
+				funcs.ResourcesDeletedWithin(30*time.Second, manifests, "mrap-specific.yaml"),
+				// MRD should still be active because wildcard MRAP still matches
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "setup/mrd.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+			)).
+
+			// Delete second MRAP, MRD should stay active.
+			Assess("DeleteWildcardMRAPMRDBecomesInactive", funcs.AllOf(
+				funcs.DeleteResources(manifests, "mrap-wildcard.yaml"),
+				funcs.ResourcesDeletedWithin(30*time.Second, manifests, "mrap-wildcard.yaml"),
+				// MRD should continue being active
+				funcs.ResourcesHaveFieldValueWithin(1*time.Minute, manifests, "setup/mrd.yaml",
+					"spec.state", string(v2alpha1.ManagedResourceDefinitionActive)),
+			)).
+
+			// Cleanup
+			WithTeardown("DeleteMultiPolicyTestMRD", funcs.AllOf(
+				funcs.DeleteResources(manifests, "setup/*.yaml"),
+				funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}
diff --git a/test/e2e/apiextensions_mrds_test.go b/test/e2e/apiextensions_mrds_test.go
new file mode 100644
index 000000000..08c0e95ef
--- /dev/null
+++ b/test/e2e/apiextensions_mrds_test.go
@@ -0,0 +1,101 @@
+/*
+Copyright 2025 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 e2e
+
+import (
+	"testing"
+	"time"
+
+	"sigs.k8s.io/e2e-framework/pkg/features"
+
+	"github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+	"github.com/crossplane/crossplane/test/e2e/config"
+	"github.com/crossplane/crossplane/test/e2e/funcs"
+)
+
+func TestMRDValidation(t *testing.T) {
+	manifests := "test/e2e/manifests/apiextensions/mrd/validation"
+
+	cases := features.Table{
+		{
+			// A valid MRD should be created.
+			Name:        "ValidNewMRDIsDefaulted",
+			Description: "A valid MRD should be created with defaults.",
+			Assessment: funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrd-defaulted.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "mrd-defaulted.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrd-defaulted.yaml", v2alpha1.InactiveManaged()),
+			),
+		},
+		{
+			// A valid MRD should be created.
+			Name:        "ValidNewMRDIsAccepted",
+			Description: "A valid MRD should be created.",
+			Assessment: funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrd-valid.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "mrd-valid.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrd-valid.yaml", v2alpha1.InactiveManaged()),
+			),
+		},
+		{
+			// An update to a valid MRD should be accepted.
+			Name:        "ValidUpdatedMRDIsAccepted",
+			Description: "A valid update to an MRD should be accepted.",
+			Assessment: funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "mrd-valid-updated.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "mrd-valid-updated.yaml"),
+				funcs.ResourcesHaveConditionWithin(1*time.Minute, manifests, "mrd-valid-updated.yaml", v2alpha1.EstablishedManaged()),
+			),
+		},
+		{
+			// An update to an invalid MRD should be rejected.
+			Name:        "InvalidMRDUpdateIsRejected",
+			Description: "An invalid update to an MRD should be rejected.",
+			Assessment:  funcs.ResourcesFailToApply(FieldManager, manifests, "mrd-valid-updated-invalid.yaml"),
+		},
+		{
+			// An update to immutable MRD fields should be rejected.
+			Name:        "ImmutableMRDFieldUpdateIsRejected",
+			Description: "An update to immutable MRD field should be rejected.",
+			Assessment:  funcs.ResourcesFailToApply(FieldManager, manifests, "mrd-immutable-updated.yaml"),
+		},
+		{
+			// An invalid MRD should be rejected.
+			Name:        "InvalidMRDIsRejected",
+			Description: "An invalid MRD should be rejected.",
+			Assessment:  funcs.ResourcesFailToApply(FieldManager, manifests, "mrd-invalid.yaml"),
+		},
+		{
+			// An attempt to deactivate an active MRD should be rejected.
+			Name:        "ActiveMRDCannotBeDeactivated",
+			Description: "An attempt to change an active MRD to inactive should be rejected.",
+			Assessment:  funcs.ResourcesFailToApply(FieldManager, manifests, "mrd-active-to-inactive.yaml"),
+		},
+	}
+	environment.Test(t,
+		cases.Build(t.Name()).
+			WithLabel(LabelArea, LabelAreaMRAP).
+			WithLabel(LabelStage, LabelStageBeta).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
+			WithTeardown("DeleteValidMRD", funcs.AllOf(
+				funcs.DeleteResources(manifests, "*-valid.yaml"),
+				funcs.ResourcesDeletedWithin(30*time.Second, manifests, "*-valid.yaml"),
+			)).
+			Feature(),
+	)
+}
diff --git a/test/e2e/funcs/env.go b/test/e2e/funcs/env.go
index d9d68f3a4..621ea384a 100644
--- a/test/e2e/funcs/env.go
+++ b/test/e2e/funcs/env.go
@@ -37,6 +37,8 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
 
 	apiextensionsv1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
+	apiextensionsv2alpha1 "github.com/crossplane/crossplane/apis/apiextensions/v2alpha1"
+	opsv1alpha1 "github.com/crossplane/crossplane/apis/ops/v1alpha1"
 	pkgv1 "github.com/crossplane/crossplane/apis/pkg/v1"
 )
 
@@ -95,6 +97,8 @@ func HelmUninstall(o ...helm.Option) env.Func {
 func AddCrossplaneTypesToScheme() env.Func {
 	return func(ctx context.Context, c *envconf.Config) (context.Context, error) {
 		_ = apiextensionsv1.AddToScheme(c.Client().Resources().GetScheme())
+		_ = opsv1alpha1.AddToScheme(c.Client().Resources().GetScheme())
+		_ = apiextensionsv2alpha1.AddToScheme(c.Client().Resources().GetScheme())
 		_ = pkgv1.AddToScheme(c.Client().Resources().GetScheme())
 
 		return ctx, nil
diff --git a/test/e2e/funcs/feature.go b/test/e2e/funcs/feature.go
index 64acd4fe9..ef855c7f3 100644
--- a/test/e2e/funcs/feature.go
+++ b/test/e2e/funcs/feature.go
@@ -334,6 +334,17 @@ func ResourcesDeletedWithin(d time.Duration, dir, pattern string, options ...dec
 	}
 }
 
+// SleepFor sleeps for the specified duration. This is useful for waiting
+// for time-based conditions to occur, such as cron schedules.
+func SleepFor(d time.Duration) features.Func {
+	return func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context {
+		t.Helper()
+		t.Logf("Sleeping for %s...", d)
+		time.Sleep(d)
+		return ctx
+	}
+}
+
 // ResourceDeletedWithin fails a test if the supplied resource is not deleted
 // within the supplied duration.
 func ResourceDeletedWithin(d time.Duration, o k8s.Object) features.Func {
@@ -486,10 +497,20 @@ func (nf notFound) String() string { return "NotFound" }
 // not be found.
 var NotFound = notFound{} //nolint:gochecknoglobals // We treat this as a constant.
 
+type anyValue struct{}
+
+func (av anyValue) String() string { return "Any" }
+
+// Any is a special 'want' value that indicates any value (except NotFound) should match.
+var Any = anyValue{} //nolint:gochecknoglobals // We treat this as a constant.
+
+// FieldValueChecker is a function that checks if a field value matches some criteria.
+type FieldValueChecker func(got any) bool
+
 // ResourcesHaveFieldValueWithin fails a test if the supplied resources do not
 // have the supplied value at the supplied field path within the supplied
 // duration. The supplied 'want' value must cmp.Equal the actual value.
-func ResourcesHaveFieldValueWithin(d time.Duration, dir, pattern, path string, want any, options ...decoder.DecodeOption) features.Func {
+func ResourcesHaveFieldValueWithin(d time.Duration, dir, pattern, path string, want any, options ...decoder.DecodeOption) features.Func { //nolint:gocognit // Only a little over.
 	return func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
 		t.Helper()
 
@@ -518,12 +539,26 @@ func ResourcesHaveFieldValueWithin(d time.Duration, dir, pattern, path string, w
 				if _, ok := want.(notFound); ok {
 					return true
 				}
+				// If we want Any but got NotFound, it doesn't match
+				if _, ok := want.(anyValue); ok {
+					return false
+				}
 			}
 
 			if err != nil {
 				return false
 			}
 
+			// If we have a custom checker function, use it
+			if checker, ok := want.(FieldValueChecker); ok {
+				return checker(got)
+			}
+
+			// If we want Any and we have a value (not NotFound), it matches
+			if _, ok := want.(anyValue); ok {
+				return true
+			}
+
 			if diff := cmp.Diff(want, got); diff != "" {
 				t.Logf("%s doesn't yet have desired value at field path %s: %s", identifier(u), path, diff)
 				return false
@@ -535,8 +570,12 @@ func ResourcesHaveFieldValueWithin(d time.Duration, dir, pattern, path string, w
 		start := time.Now()
 
 		if err := wait.For(conditions.New(c.Client().Resources()).ResourcesMatch(list, match), wait.WithTimeout(d), wait.WithInterval(DefaultPollInterval)); err != nil {
-			y, _ := yaml.Marshal(list.Items)
-			t.Errorf("resources did not have desired value %q at field path %s: %v:\n\n%s\n\n", want, path, err, y)
+			objs := make([]client.Object, len(list.Items))
+			for i := range list.Items {
+				objs[i] = &list.Items[i]
+			}
+
+			t.Errorf("resources did not have desired value %q at field path %s: %v:\n\n%s\n\n", want, path, err, toYAML(objs...))
 
 			return ctx
 		}
@@ -569,12 +608,26 @@ func ResourceHasFieldValueWithin(d time.Duration, o k8s.Object, path string, wan
 				if _, ok := want.(notFound); ok {
 					return true
 				}
+				// If we want Any but got NotFound, it doesn't match
+				if _, ok := want.(anyValue); ok {
+					return false
+				}
 			}
 
 			if err != nil {
 				return false
 			}
 
+			// If we have a custom checker function, use it
+			if checker, ok := want.(FieldValueChecker); ok {
+				return checker(got)
+			}
+
+			// If we want Any and we have a value (not NotFound), it matches
+			if _, ok := want.(anyValue); ok {
+				return true
+			}
+
 			if diff := cmp.Diff(want, got); diff != "" {
 				t.Logf("%s doesn't yet have desired value at field path %s: %s", identifier(u), path, diff)
 				return false
@@ -902,7 +955,7 @@ func CompositeResourceMustMatchWithin(d time.Duration, dir, claimFile string, ma
 // CompositeResourceHasFieldValueWithin asserts that the XR referred to by the
 // claim in the given file has the specified value at the specified path within
 // the specified time.
-func CompositeResourceHasFieldValueWithin(d time.Duration, dir, claimFile, path string, want any, options ...decoder.DecodeOption) features.Func {
+func CompositeResourceHasFieldValueWithin(d time.Duration, dir, claimFile, path string, want any, options ...decoder.DecodeOption) features.Func { //nolint:gocognit // Only a little over.
 	return func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
 		t.Helper()
 
@@ -945,12 +998,26 @@ func CompositeResourceHasFieldValueWithin(d time.Duration, dir, claimFile, path
 				if _, ok := want.(notFound); ok {
 					return true
 				}
+				// If we want Any but got NotFound, it doesn't match
+				if _, ok := want.(anyValue); ok {
+					return false
+				}
 			}
 
 			if err != nil {
 				return false
 			}
 
+			// If we have a custom checker function, use it
+			if checker, ok := want.(FieldValueChecker); ok {
+				return checker(got)
+			}
+
+			// If we want Any and we have a value (not NotFound), it matches
+			if _, ok := want.(anyValue); ok {
+				return true
+			}
+
 			if diff := cmp.Diff(want, got); diff != "" {
 				t.Logf("%s doesn't yet have desired value at field path %s: %s", identifier(xr), path, diff)
 				return false
@@ -1048,12 +1115,26 @@ func ComposedResourcesHaveFieldValueWithin(d time.Duration, dir, file, path stri
 				if _, ok := want.(notFound); ok {
 					return true
 				}
+				// If we want Any but got NotFound, it doesn't match
+				if _, ok := want.(anyValue); ok {
+					return false
+				}
 			}
 
 			if err != nil {
 				return false
 			}
 
+			// If we have a custom checker function, use it
+			if checker, ok := want.(FieldValueChecker); ok {
+				return checker(got)
+			}
+
+			// If we want Any and we have a value (not NotFound), it matches
+			if _, ok := want.(anyValue); ok {
+				return true
+			}
+
 			if diff := cmp.Diff(want, got); diff != "" {
 				t.Logf("%s doesn't yet have desired value at field path %s: %s", identifier(u), path, diff)
 				return false
diff --git a/test/e2e/install_test.go b/test/e2e/install_test.go
index 0fdbc391f..d9dbcaa51 100644
--- a/test/e2e/install_test.go
+++ b/test/e2e/install_test.go
@@ -18,6 +18,7 @@ package e2e
 
 import (
 	"context"
+	"strings"
 	"testing"
 	"time"
 
@@ -156,14 +157,18 @@ func TestCrossplaneLifecycle(t *testing.T) {
 			//  Tracking issue https://github.com/crossplane/crossplane/issues/6506
 			Assess("DeleteProviderNopDeployment", func(ctx context.Context, t *testing.T, e *envconf.Config) context.Context {
 				t.Helper()
-				err := e.Client().Resources("crossplane-system").Delete(ctx, &appsv1.Deployment{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "provider-nop-lifecycle-upgrade-37f3300ebfa7",
-						Namespace: "crossplane-system",
-					},
-				})
+				ds := &appsv1.DeploymentList{}
+				err := e.Client().Resources("crossplane-system").List(ctx, ds)
 				if client.IgnoreNotFound(err) != nil {
-					t.Errorf("Failed to delete provider-nop-lifecycle-upgrade-37f3300ebfa7 deployment: %v", err)
+					t.Errorf("Failed to list deployments: %v", err)
+				}
+				// Look for anything with the prefix "provider-nop-lifecycle-upgrade" and delete it.
+				for _, d := range ds.Items {
+					if strings.HasPrefix(d.Name, "provider-nop-lifecycle-upgrade") {
+						if err := e.Client().Resources("crossplane-system").Delete(ctx, &d); client.IgnoreNotFound(err) != nil {
+							t.Errorf("Failed to delete %s deployment: %v", d.Name, err)
+						}
+					}
 				}
 				return ctx
 			}).
@@ -184,7 +189,14 @@ func TestCrossplaneLifecycle(t *testing.T) {
 				}))),
 			)).
 			WithTeardown("DeletePrerequisites", funcs.AllOf(
-				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/*.yaml", metav1.DeletePropagationForeground),
+				// TODO(lsviben): We are deleting the XRD without foreground deletion as it
+				// messes up the deletion, when the XRD version downgrades from v2 to v1. For
+				// some reason, the XRD deletion gets stuck, as the garbage collector is still
+				// trying to delete the XRD with v2 version, which is not available anymore.
+				funcs.DeleteResources(manifests, "setup/definition.yaml"),
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/composition.yaml", metav1.DeletePropagationForeground),
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/functions.yaml", metav1.DeletePropagationForeground),
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/provider.yaml", metav1.DeletePropagationForeground),
 				funcs.ResourcesDeletedWithin(3*time.Minute, manifests, "setup/*.yaml"),
 
 				// TODO(negz): We're seeing revisions sticking
diff --git a/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/mrap-specific.yaml b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/mrap-specific.yaml
new file mode 100644
index 000000000..cb8084b1f
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/mrap-specific.yaml
@@ -0,0 +1,7 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceActivationPolicy
+metadata:
+  name: activation-test-specific-policy
+spec:
+  activate:
+  - storages.multiple.activation-e2e.crossplane.io
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/mrap-wildcard.yaml b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/mrap-wildcard.yaml
new file mode 100644
index 000000000..06c146177
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/mrap-wildcard.yaml
@@ -0,0 +1,7 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceActivationPolicy
+metadata:
+  name: activation-test-wildcard-policy
+spec:
+  activate:
+  - "*.multiple.activation-e2e.crossplane.io"
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/setup/mrd.yaml b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/setup/mrd.yaml
new file mode 100644
index 000000000..44dc3ace2
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/setup/mrd.yaml
@@ -0,0 +1,34 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: storages.multiple.activation-e2e.crossplane.io
+spec:
+  group: multiple.activation-e2e.crossplane.io
+  scope: Namespaced
+  names:
+    kind: Storage
+    plural: storages
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              capacity:
+                type: string
+              tier:
+                type: string
+            required:
+            - capacity
+            - tier
+          status:
+            type: object
+            properties:
+              storageId:
+                type: string
+  state: Inactive
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/single-activation/expected-crd.yaml b/test/e2e/manifests/apiextensions/activation-policy/single-activation/expected-crd.yaml
new file mode 100644
index 000000000..26767a18b
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/single-activation/expected-crd.yaml
@@ -0,0 +1,34 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: buckets.single.activation-e2e.crossplane.io
+spec:
+  group: single.activation-e2e.crossplane.io
+  scope: Namespaced
+  names:
+    plural: buckets
+    singular: bucket
+    kind: Bucket
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              region:
+                type: string
+              name:
+                type: string
+            required:
+            - region
+            - name
+          status:
+            type: object
+            properties:
+              bucketName:
+                type: string
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/single-activation/mrap.yaml b/test/e2e/manifests/apiextensions/activation-policy/single-activation/mrap.yaml
new file mode 100644
index 000000000..b8ad09732
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/single-activation/mrap.yaml
@@ -0,0 +1,7 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceActivationPolicy
+metadata:
+  name: activation-test-single-policy
+spec:
+  activate:
+  - buckets.single.activation-e2e.crossplane.io
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrap-disabled.yaml b/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrap-disabled.yaml
new file mode 100644
index 000000000..e833e5d5a
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrap-disabled.yaml
@@ -0,0 +1,7 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceActivationPolicy
+metadata:
+  name: activation-test-single-policy
+spec:
+  activate:
+  - nonexistent.disabled.activation-e2e.crossplane.io
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrd.yaml b/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrd.yaml
new file mode 100644
index 000000000..dbee2f50b
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrd.yaml
@@ -0,0 +1,35 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: buckets.single.activation-e2e.crossplane.io
+spec:
+  group: single.activation-e2e.crossplane.io
+  scope: Namespaced
+  names:
+    plural: buckets
+    singular: bucket
+    kind: Bucket
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              region:
+                type: string
+              name:
+                type: string
+            required:
+            - region
+            - name
+          status:
+            type: object
+            properties:
+              bucketName:
+                type: string
+  state: Inactive
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/bucket-crd.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/bucket-crd.yaml
new file mode 100644
index 000000000..49e845e18
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/bucket-crd.yaml
@@ -0,0 +1,31 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: buckets.wildcard.activation-e2e.crossplane.io
+spec:
+  group: wildcard.activation-e2e.crossplane.io
+  scope: Namespaced
+  names:
+    plural: buckets
+    singular: bucket
+    kind: Bucket
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              region:
+                type: string
+            required:
+            - region
+          status:
+            type: object
+            properties:
+              bucketName:
+                type: string
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/database-crd.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/database-crd.yaml
new file mode 100644
index 000000000..65923f588
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/database-crd.yaml
@@ -0,0 +1,34 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: databases.wildcard.activation-e2e.crossplane.io
+spec:
+  group: wildcard.activation-e2e.crossplane.io
+  scope: Namespaced
+  names:
+    plural: databases
+    singular: database
+    kind: Database
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              engine:
+                type: string
+              size:
+                type: string
+            required:
+            - engine
+            - size
+          status:
+            type: object
+            properties:
+              endpoint:
+                type: string
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/instance-crd.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/instance-crd.yaml
new file mode 100644
index 000000000..5c61467b8
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/expected-crds/instance-crd.yaml
@@ -0,0 +1,34 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: instances.wildcard.activation-e2e.crossplane.io
+spec:
+  group: wildcard.activation-e2e.crossplane.io
+  scope: Namespaced
+  names:
+    plural: instances
+    singular: instance
+    kind: Instance
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              instanceType:
+                type: string
+              region:
+                type: string
+            required:
+            - instanceType
+            - region
+          status:
+            type: object
+            properties:
+              instanceId:
+                type: string
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/mrap-wildcard.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/mrap-wildcard.yaml
new file mode 100644
index 000000000..c1be5c822
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/mrap-wildcard.yaml
@@ -0,0 +1,7 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceActivationPolicy
+metadata:
+  name: activation-test-wildcard-policy
+spec:
+  activate:
+  - "*.wildcard.activation-e2e.crossplane.io"
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-bucket.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-bucket.yaml
new file mode 100644
index 000000000..e0264f67f
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-bucket.yaml
@@ -0,0 +1,31 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: buckets.wildcard.activation-e2e.crossplane.io
+spec:
+  scope: Namespaced
+  group: wildcard.activation-e2e.crossplane.io
+  names:
+    kind: Bucket
+    plural: buckets
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              region:
+                type: string
+            required:
+            - region
+          status:
+            type: object
+            properties:
+              bucketName:
+                type: string
+  state: Inactive
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-database.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-database.yaml
new file mode 100644
index 000000000..3cf4cc648
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-database.yaml
@@ -0,0 +1,34 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: databases.wildcard.activation-e2e.crossplane.io
+spec:
+  scope: Namespaced
+  group: wildcard.activation-e2e.crossplane.io
+  names:
+    kind: Database
+    plural: databases
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              engine:
+                type: string
+              size:
+                type: string
+            required:
+            - engine
+            - size
+          status:
+            type: object
+            properties:
+              endpoint:
+                type: string
+  state: Inactive
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-instance.yaml b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-instance.yaml
new file mode 100644
index 000000000..8ff61130f
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-instance.yaml
@@ -0,0 +1,34 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: instances.wildcard.activation-e2e.crossplane.io
+spec:
+  scope: Namespaced
+  group: wildcard.activation-e2e.crossplane.io
+  names:
+    kind: Instance
+    plural: instances
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          spec:
+            type: object
+            properties:
+              instanceType:
+                type: string
+              region:
+                type: string
+            required:
+            - instanceType
+            - region
+          status:
+            type: object
+            properties:
+              instanceId:
+                type: string
+  state: Inactive
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/composition/basic-cluster/setup/definition.yaml b/test/e2e/manifests/apiextensions/composition/basic-cluster/setup/definition.yaml
index 75ddbc4ad..ffddfd854 100644
--- a/test/e2e/manifests/apiextensions/composition/basic-cluster/setup/definition.yaml
+++ b/test/e2e/manifests/apiextensions/composition/basic-cluster/setup/definition.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v2alpha1
+apiVersion: apiextensions.crossplane.io/v2
 kind: CompositeResourceDefinition
 metadata:
   name: clustertests.example.org
diff --git a/test/e2e/manifests/apiextensions/composition/basic-namespaced/setup/definition.yaml b/test/e2e/manifests/apiextensions/composition/basic-namespaced/setup/definition.yaml
index aba3e0e81..9d909d6b6 100644
--- a/test/e2e/manifests/apiextensions/composition/basic-namespaced/setup/definition.yaml
+++ b/test/e2e/manifests/apiextensions/composition/basic-namespaced/setup/definition.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v2alpha1
+apiVersion: apiextensions.crossplane.io/v2
 kind: CompositeResourceDefinition
 metadata:
   name: tests.example.org
diff --git a/test/e2e/manifests/apiextensions/composition/namespaced-xr-no-cluster-scoped-resource/setup/definition.yaml b/test/e2e/manifests/apiextensions/composition/namespaced-xr-no-cluster-scoped-resource/setup/definition.yaml
index 109eaeebf..0df1208bb 100644
--- a/test/e2e/manifests/apiextensions/composition/namespaced-xr-no-cluster-scoped-resource/setup/definition.yaml
+++ b/test/e2e/manifests/apiextensions/composition/namespaced-xr-no-cluster-scoped-resource/setup/definition.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v2alpha1
+apiVersion: apiextensions.crossplane.io/v2
 kind: CompositeResourceDefinition
 metadata:
   name: namespacedtests.validation.example.org
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-active-to-inactive.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-active-to-inactive.yaml
new file mode 100644
index 000000000..b5c004c78
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-active-to-inactive.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  state: Inactive
+  scope: Namespaced
+  group: nop.mrd.example.org
+  names:
+    kind: XNopResource
+    plural: xnopresources
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
\ No newline at end of file
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-defaulted.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-defaulted.yaml
new file mode 100644
index 000000000..e55f087ff
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-defaulted.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  # state: Inactive <-- This will be defaulted.
+  scope: Namespaced
+  group: nop.mrd.example.org
+  names:
+    kind: XNopResource
+    plural: xnopresources
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-immutable-updated.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-immutable-updated.yaml
new file mode 100644
index 000000000..b5bcb4a34
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-immutable-updated.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  state: Inactive
+  scope: Namespaced
+  group: nope.mrd.example.org # <-- invalid, field is immutable
+  names:
+    kind: XNopResource
+    plural: xnopresources
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-invalid.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-invalid.yaml
new file mode 100644
index 000000000..6118888f2
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-invalid.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  state: Inactive
+  scope: Namespaced
+  group: nop.mrd.example.org
+  names:
+    kind: XNopResource
+    plural: Nopresourcestwo # <-- invalid, should be lowercase
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid-updated-invalid.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid-updated-invalid.yaml
new file mode 100644
index 000000000..a2b808da6
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid-updated-invalid.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  state: Inactive
+  scope: Namespaced
+  group: nop.mrd.example.org
+  names:
+    kind: XNopResource
+    plural: Nopresources # <-- invalid, should be lowercase and immutable
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid-updated.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid-updated.yaml
new file mode 100644
index 000000000..799bb4581
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid-updated.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  state: Active
+  scope: Namespaced
+  group: nop.mrd.example.org
+  names:
+    kind: XNopResource
+    plural: xnopresources
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
diff --git a/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid.yaml b/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid.yaml
new file mode 100644
index 000000000..c3b5ec3b6
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/mrd/validation/mrd-valid.yaml
@@ -0,0 +1,29 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: xnopresources.nop.mrd.example.org
+spec:
+  state: Inactive
+  scope: Namespaced
+  group: nop.mrd.example.org
+  names:
+    kind: XNopResource
+    plural: xnopresources
+    singular: xnopresource
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+      subresources:
+        status: { }
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                coolField:
+                  type: string
+              required:
+                - coolField
diff --git a/test/e2e/manifests/ops/cronoperations/scheduling/cronoperation.yaml b/test/e2e/manifests/ops/cronoperations/scheduling/cronoperation.yaml
new file mode 100644
index 000000000..dc74908f1
--- /dev/null
+++ b/test/e2e/manifests/ops/cronoperations/scheduling/cronoperation.yaml
@@ -0,0 +1,26 @@
+apiVersion: ops.crossplane.io/v1alpha1
+kind: CronOperation
+metadata:
+  name: basic-cronop
+spec:
+  schedule: "* * * * *"  # Every minute for fast testing
+  startingDeadlineSeconds: 30
+  concurrencyPolicy: Allow
+  successfulHistoryLimit: 2
+  failedHistoryLimit: 1
+  operationTemplate:
+    spec:
+      mode: Pipeline
+      pipeline:
+      - step: nop
+        functionRef:
+          name: function-dummy-cronops-basic
+        input:
+          apiVersion: dummy.fn.crossplane.io/v1beta1
+          kind: Response
+          # This is a YAML-serialized RunFunctionResponse. function-dummy will
+          # overlay the desired state on any that was passed into it.
+          response:
+            results:
+             - severity: SEVERITY_NORMAL
+               message: "CronOperation ran me!"
diff --git a/test/e2e/manifests/ops/cronoperations/scheduling/setup/functions.yaml b/test/e2e/manifests/ops/cronoperations/scheduling/setup/functions.yaml
new file mode 100644
index 000000000..8207d3516
--- /dev/null
+++ b/test/e2e/manifests/ops/cronoperations/scheduling/setup/functions.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: pkg.crossplane.io/v1
+kind: Function
+metadata:
+  name: function-dummy-cronops-basic
+spec:
+  package: xpkg.crossplane.io/crossplane-contrib/function-dummy:v0.4.1
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/operations/pipeline/operation.yaml b/test/e2e/manifests/ops/operations/pipeline/operation.yaml
new file mode 100644
index 000000000..d08efa55c
--- /dev/null
+++ b/test/e2e/manifests/ops/operations/pipeline/operation.yaml
@@ -0,0 +1,65 @@
+apiVersion: ops.crossplane.io/v1alpha1
+kind: Operation
+metadata:
+  name: multi-step-test
+spec:
+  mode: Pipeline
+  retryLimit: 5
+  pipeline:
+  - step: create-configmap-a
+    functionRef:
+      name: function-dummy-ops-multi-step
+    input:
+      apiVersion: dummy.fn.crossplane.io/v1beta1
+      kind: Response
+      response:
+        # First function creates ConfigMap A and outputs step info
+        desired:
+          resources:
+            configmap-a:
+              resource:
+                apiVersion: v1
+                kind: ConfigMap
+                metadata:
+                  namespace: default
+                  name: multi-step-configmap-a
+                data:
+                  step: "1"
+                  content: "Created by step 1"
+        # Output that should be captured in Operation status
+        output:
+          stepName: "create-configmap-a"
+          resourcesCreated: 1
+          configMapName: "multi-step-configmap-a"
+        results:
+        - severity: SEVERITY_NORMAL
+          message: "Step 1: Created ConfigMap A"
+  - step: create-configmap-b
+    functionRef:
+      name: function-dummy-ops-multi-step
+    input:
+      apiVersion: dummy.fn.crossplane.io/v1beta1
+      kind: Response
+      response:
+        # Second function creates ConfigMap B (overlays on top of A)
+        desired:
+          resources:
+            configmap-b:
+              resource:
+                apiVersion: v1
+                kind: ConfigMap
+                metadata:
+                  namespace: default
+                  name: multi-step-configmap-b
+                data:
+                  step: "2"
+                  content: "Created by step 2"
+        # Output that should be captured in Operation status
+        output:
+          stepName: "create-configmap-b"
+          resourcesCreated: 1
+          configMapName: "multi-step-configmap-b"
+          totalPipelineResources: 2
+        results:
+        - severity: SEVERITY_NORMAL
+          message: "Step 2: Created ConfigMap B, pipeline complete"
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/operations/pipeline/setup/functions.yaml b/test/e2e/manifests/ops/operations/pipeline/setup/functions.yaml
new file mode 100644
index 000000000..6ff359f35
--- /dev/null
+++ b/test/e2e/manifests/ops/operations/pipeline/setup/functions.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: pkg.crossplane.io/v1beta1
+kind: Function
+metadata:
+  name: function-dummy-ops-multi-step
+spec:
+  package: xpkg.crossplane.io/crossplane-contrib/function-dummy:v0.4.1
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/operations/retry/operation.yaml b/test/e2e/manifests/ops/operations/retry/operation.yaml
new file mode 100644
index 000000000..1d086e804
--- /dev/null
+++ b/test/e2e/manifests/ops/operations/retry/operation.yaml
@@ -0,0 +1,19 @@
+apiVersion: ops.crossplane.io/v1alpha1
+kind: Operation
+metadata:
+  name: retry-test
+spec:
+  mode: Pipeline
+  retryLimit: 3
+  pipeline:
+  - step: always-fail
+    functionRef:
+      name: function-dummy-ops-retry
+    input:
+      apiVersion: dummy.fn.crossplane.io/v1beta1
+      kind: Response
+      # This response will always cause the function to fail
+      response:
+        results:
+        - severity: SEVERITY_FATAL
+          message: "Simulated failure for retry testing"
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/operations/retry/setup/functions.yaml b/test/e2e/manifests/ops/operations/retry/setup/functions.yaml
new file mode 100644
index 000000000..866ca73ec
--- /dev/null
+++ b/test/e2e/manifests/ops/operations/retry/setup/functions.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: pkg.crossplane.io/v1beta1
+kind: Function
+metadata:
+  name: function-dummy-ops-retry
+spec:
+  package: xpkg.crossplane.io/crossplane-contrib/function-dummy:v0.4.1
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/operations/basic/operation.yaml b/test/e2e/manifests/ops/operations/simple/operation.yaml
similarity index 100%
rename from test/e2e/manifests/ops/operations/basic/operation.yaml
rename to test/e2e/manifests/ops/operations/simple/operation.yaml
diff --git a/test/e2e/manifests/ops/operations/basic/setup/functions.yaml b/test/e2e/manifests/ops/operations/simple/setup/functions.yaml
similarity index 100%
rename from test/e2e/manifests/ops/operations/basic/setup/functions.yaml
rename to test/e2e/manifests/ops/operations/simple/setup/functions.yaml
diff --git a/test/e2e/manifests/ops/operations/basic/setup/secret.yaml b/test/e2e/manifests/ops/operations/simple/setup/secret.yaml
similarity index 100%
rename from test/e2e/manifests/ops/operations/basic/setup/secret.yaml
rename to test/e2e/manifests/ops/operations/simple/setup/secret.yaml
diff --git a/test/e2e/manifests/ops/watchoperations/resource-changes/setup/functions.yaml b/test/e2e/manifests/ops/watchoperations/resource-changes/setup/functions.yaml
new file mode 100644
index 000000000..7b039364b
--- /dev/null
+++ b/test/e2e/manifests/ops/watchoperations/resource-changes/setup/functions.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: pkg.crossplane.io/v1
+kind: Function
+metadata:
+  name: function-dummy-watchops-basic
+spec:
+  package: xpkg.crossplane.io/crossplane-contrib/function-dummy:v0.4.1
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap-second.yaml b/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap-second.yaml
new file mode 100644
index 000000000..3f829d802
--- /dev/null
+++ b/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap-second.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: test-configmap-second
+  namespace: default
+  labels:
+    test: basic-watchop
+data:
+  testData: "I'm the second ConfigMap that triggers WatchOperations!"
+  resourceNumber: "2"
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap-updated.yaml b/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap-updated.yaml
new file mode 100644
index 000000000..7ed1fcb3f
--- /dev/null
+++ b/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap-updated.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: test-configmap
+  namespace: default
+  labels:
+    test: basic-watchop
+data:
+  testData: "I trigger WatchOperations again!"
+  updateNumber: "1"
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap.yaml b/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap.yaml
new file mode 100644
index 000000000..36b96407f
--- /dev/null
+++ b/test/e2e/manifests/ops/watchoperations/resource-changes/test-configmap.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: test-configmap
+  namespace: default
+  labels:
+    test: basic-watchop
+data:
+  testData: "I trigger WatchOperations!"
\ No newline at end of file
diff --git a/test/e2e/manifests/ops/watchoperations/resource-changes/watchoperation.yaml b/test/e2e/manifests/ops/watchoperations/resource-changes/watchoperation.yaml
new file mode 100644
index 000000000..4658b849f
--- /dev/null
+++ b/test/e2e/manifests/ops/watchoperations/resource-changes/watchoperation.yaml
@@ -0,0 +1,30 @@
+apiVersion: ops.crossplane.io/v1alpha1
+kind: WatchOperation
+metadata:
+  name: basic-watchop
+spec:
+  watch:
+    apiVersion: v1
+    kind: ConfigMap
+    namespace: default
+    matchLabels:
+      test: basic-watchop
+  concurrencyPolicy: Allow
+  successfulHistoryLimit: 10
+  failedHistoryLimit: 5
+  operationTemplate:
+    spec:
+      mode: Pipeline
+      pipeline:
+      - step: nop
+        functionRef:
+          name: function-dummy-watchops-basic
+        input:
+          apiVersion: dummy.fn.crossplane.io/v1beta1
+          kind: Response
+          # This is a YAML-serialized RunFunctionResponse. function-dummy will
+          # overlay the desired state on any that was passed into it.
+          response:
+            results:
+             - severity: SEVERITY_NORMAL
+               message: "WatchOperation ran me!"
\ No newline at end of file
diff --git a/test/e2e/manifests/pkg/deployment-runtime-config/setup/definition.yaml b/test/e2e/manifests/pkg/deployment-runtime-config/setup/definition.yaml
index aba3e0e81..9d909d6b6 100644
--- a/test/e2e/manifests/pkg/deployment-runtime-config/setup/definition.yaml
+++ b/test/e2e/manifests/pkg/deployment-runtime-config/setup/definition.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v2alpha1
+apiVersion: apiextensions.crossplane.io/v2
 kind: CompositeResourceDefinition
 metadata:
   name: tests.example.org
diff --git a/test/e2e/manifests/pkg/externally-managed-service-account/setup/definition.yaml b/test/e2e/manifests/pkg/externally-managed-service-account/setup/definition.yaml
index aba3e0e81..9d909d6b6 100644
--- a/test/e2e/manifests/pkg/externally-managed-service-account/setup/definition.yaml
+++ b/test/e2e/manifests/pkg/externally-managed-service-account/setup/definition.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v2alpha1
+apiVersion: apiextensions.crossplane.io/v2
 kind: CompositeResourceDefinition
 metadata:
   name: tests.example.org
diff --git a/test/e2e/ops_operations_test.go b/test/e2e/ops_operations_test.go
index ed2fc026e..d6fd366c1 100644
--- a/test/e2e/ops_operations_test.go
+++ b/test/e2e/ops_operations_test.go
@@ -22,10 +22,14 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/e2e-framework/klient/k8s"
 	"sigs.k8s.io/e2e-framework/pkg/features"
 	"sigs.k8s.io/e2e-framework/third_party/helm"
 
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+
 	"github.com/crossplane/crossplane/apis/ops/v1alpha1"
+	pkgv1 "github.com/crossplane/crossplane/apis/pkg/v1"
 	"github.com/crossplane/crossplane/test/e2e/config"
 	"github.com/crossplane/crossplane/test/e2e/funcs"
 )
@@ -49,10 +53,10 @@ func init() {
 	)
 }
 
-func TestBasicOperation(t *testing.T) {
+func TestOperation(t *testing.T) {
 	cm := &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "cool-map"}}
 
-	manifests := "test/e2e/manifests/ops/operations/basic"
+	manifests := "test/e2e/manifests/ops/operations/simple"
 	environment.Test(t,
 		features.NewWithDescription(t.Name(), "Tests the correct functioning of a basic Operation that creates a ConfigMap.").
 			WithLabel(LabelArea, LabelAreaOps).
@@ -61,22 +65,17 @@ func TestBasicOperation(t *testing.T) {
 			WithSetup("CreatePrerequisites", funcs.AllOf(
 				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
 				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				// Wait for function to be ready with capabilities before creating Operation
+				funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "setup/functions.yaml", pkgv1.Healthy(), pkgv1.Active()),
 			)).
 			Assess("CreateOperation", funcs.AllOf(
 				funcs.ApplyResources(FieldManager, manifests, "operation.yaml"),
 				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "operation.yaml"),
 			)).
 			Assess("OperationSucceeded", funcs.AllOf(
-				// This takes a little longer because it needs
-				// the FunctionRevision above to become ready,
-				// but it strictly retries with exponential
-				// backoff. It doesn't watch FunctionRevisions.
 				funcs.ResourcesHaveConditionWithin(60*time.Second, manifests, "operation.yaml", v1alpha1.Complete()),
 				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.appliedResourceRefs[0].name", "cool-map"),
 				funcs.ResourceHasFieldValueWithin(30*time.Second, cm, "data[coolData]", "I'm cool!"),
-				// TODO(negz): Test function output when we have
-				// a function-dummy release containing this PR.
-				// https://github.com/crossplane-contrib/function-dummy/pull/42
 			)).
 			WithTeardown("DeleteOperation", funcs.AllOf(
 				funcs.DeleteResources(manifests, "operation.yaml"),
@@ -89,3 +88,327 @@ func TestBasicOperation(t *testing.T) {
 			Feature(),
 	)
 }
+
+func TestOperationRetryLogic(t *testing.T) {
+	manifests := "test/e2e/manifests/ops/operations/retry"
+	environment.Test(t,
+		features.NewWithDescription(t.Name(), "Tests the retry logic of Operations when function pipelines fail.").
+			WithLabel(LabelArea, LabelAreaOps).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, SuiteOps).
+			WithSetup("CreatePrerequisites", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				// Wait for function to be ready with capabilities before creating Operation
+				funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "setup/*.yaml", pkgv1.Healthy(), pkgv1.Active()),
+			)).
+			Assess("CreateOperation", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "operation.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "operation.yaml"),
+			)).
+			Assess("OperationRetriesAndEventuallyFails", funcs.AllOf(
+				// Wait for Operation to start retrying - it should start with 0 failures
+				funcs.ResourcesHaveConditionWithin(30*time.Second, manifests, "operation.yaml", xpv1.ReconcileSuccess()),
+				// Wait for Operation to accumulate failures as it retries (exponential backoff means this takes time)
+				// retryLimit is 3, so it should eventually reach 3 failures and be marked as failed
+				funcs.ResourcesHaveFieldValueWithin(3*time.Minute, manifests, "operation.yaml", "status.failures", int64(3)),
+				// Operation should eventually be marked as failed after reaching retry limit
+				funcs.ResourcesHaveConditionWithin(30*time.Second, manifests, "operation.yaml", v1alpha1.Failed("")),
+			)).
+			WithTeardown("DeleteOperation", funcs.AllOf(
+				funcs.DeleteResources(manifests, "operation.yaml"),
+				funcs.ResourcesDeletedWithin(2*time.Minute, manifests, "operation.yaml"),
+			)).
+			WithTeardown("DeletePrerequisites", funcs.AllOf(
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/*.yaml", metav1.DeletePropagationForeground),
+				funcs.ResourcesDeletedWithin(3*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}
+
+func TestOperationMultiStepPipeline(t *testing.T) {
+	cmA := &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "multi-step-configmap-a"}}
+	cmB := &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "multi-step-configmap-b"}}
+
+	manifests := "test/e2e/manifests/ops/operations/pipeline"
+	environment.Test(t,
+		features.NewWithDescription(t.Name(), "Tests multi-step pipeline execution with function output capture.").
+			WithLabel(LabelArea, LabelAreaOps).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, SuiteOps).
+			WithSetup("CreatePrerequisites", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				// Wait for function to be ready with capabilities before creating Operation
+				funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "setup/*.yaml", pkgv1.Healthy(), pkgv1.Active()),
+			)).
+			Assess("CreateOperation", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "operation.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "operation.yaml"),
+			)).
+			Assess("OperationExecutesPipelineSuccessfully", funcs.AllOf(
+				// Wait for Operation to become ready and succeed
+				funcs.ResourcesHaveConditionWithin(60*time.Second, manifests, "operation.yaml", v1alpha1.Complete()),
+				// Verify both ConfigMaps were created by the pipeline
+				funcs.ResourceHasFieldValueWithin(30*time.Second, cmA, "data[step]", "1"),
+				funcs.ResourceHasFieldValueWithin(30*time.Second, cmA, "data[content]", "Created by step 1"),
+				funcs.ResourceHasFieldValueWithin(30*time.Second, cmB, "data[step]", "2"),
+				funcs.ResourceHasFieldValueWithin(30*time.Second, cmB, "data[content]", "Created by step 2"),
+				// Verify appliedResourceRefs contains both ConfigMaps
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.appliedResourceRefs[0].name", "multi-step-configmap-a"),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.appliedResourceRefs[1].name", "multi-step-configmap-b"),
+			)).
+			Assess("OperationCapturesFunctionOutputs", funcs.AllOf(
+				// Verify function outputs are captured in Operation status
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.pipeline[0].step", "create-configmap-a"),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.pipeline[0].output.stepName", "create-configmap-a"),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.pipeline[0].output.resourcesCreated", int64(1)),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.pipeline[1].step", "create-configmap-b"),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.pipeline[1].output.stepName", "create-configmap-b"),
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "operation.yaml", "status.pipeline[1].output.totalPipelineResources", int64(2)),
+			)).
+			WithTeardown("DeleteOperation", funcs.AllOf(
+				funcs.DeleteResources(manifests, "operation.yaml"),
+				funcs.ResourcesDeletedWithin(2*time.Minute, manifests, "operation.yaml"),
+			)).
+			WithTeardown("DeletePrerequisites", funcs.AllOf(
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/*.yaml", metav1.DeletePropagationForeground),
+				funcs.ResourcesDeletedWithin(3*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}
+
+func TestCronOperationScheduling(t *testing.T) {
+	var firstScheduleTime, firstSuccessTime time.Time
+
+	manifests := "test/e2e/manifests/ops/cronoperations/scheduling"
+	environment.Test(t,
+		features.NewWithDescription(t.Name(), "Tests CronOperation scheduling behavior, validating that it creates multiple Operations on schedule over time.").
+			WithLabel(LabelArea, LabelAreaOps).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, SuiteOps).
+			WithSetup("CreatePrerequisites", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				// Wait for function to be ready with capabilities before creating Operation
+				funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "setup/*.yaml", pkgv1.Healthy(), pkgv1.Active()),
+			)).
+			Assess("CreateCronOperation", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "cronoperation.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "cronoperation.yaml"),
+			)).
+			Assess("CronOperationCreatesFirstOperation", funcs.AllOf(
+				// Capture the first lastScheduleTime when CronOperation creates its first Operation
+				// Since the schedule is every minute, this should happen quickly
+				funcs.ResourcesHaveFieldValueWithin(90*time.Second, manifests, "cronoperation.yaml", "status.lastScheduleTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								firstScheduleTime = parsed
+								return true
+							}
+						}
+						return false
+					})),
+				// Verify the CronOperation has the correct status and schedule is active
+				funcs.ResourcesHaveConditionWithin(30*time.Second, manifests, "cronoperation.yaml", xpv1.ReconcileSuccess(), v1alpha1.ScheduleActive()),
+			)).
+			Assess("FirstOperationSucceeds", funcs.AllOf(
+				// Capture the first lastSuccessfulTime when the first Operation completes
+				funcs.ResourcesHaveFieldValueWithin(60*time.Second, manifests, "cronoperation.yaml", "status.lastSuccessfulTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								firstSuccessTime = parsed
+								return true
+							}
+						}
+						return false
+					})),
+			)).
+			Assess("CronOperationCreatesSecondOperation", funcs.AllOf(
+				// Wait for the second cron trigger to happen and create another Operation
+				// Verify the lastScheduleTime advances, proving a new Operation was created
+				// Since the schedule is every minute, we wait up to 2 minutes for the next trigger
+				funcs.ResourcesHaveFieldValueWithin(2*time.Minute, manifests, "cronoperation.yaml", "status.lastScheduleTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								return parsed.After(firstScheduleTime)
+							}
+						}
+						return false
+					})),
+			)).
+			Assess("SecondOperationSucceeds", funcs.AllOf(
+				// Verify the second Operation also completes successfully
+				// Check that lastSuccessfulTime advances from the first success time
+				funcs.ResourcesHaveFieldValueWithin(90*time.Second, manifests, "cronoperation.yaml", "status.lastSuccessfulTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								return parsed.After(firstSuccessTime)
+							}
+						}
+						return false
+					})),
+			)).
+			Assess("ValidateMultipleOperations", funcs.AllOf(
+				// As a final validation, verify that we have the expected number of Operations
+				// We expect at least 2 Operations: one from each cron trigger
+				funcs.ListedResourcesValidatedWithin(30*time.Second,
+					&v1alpha1.OperationList{},
+					2, // At least 2 Operations should exist
+					func(o k8s.Object) bool {
+						// Check if this Operation was created by our CronOperation
+						return o.GetLabels()[v1alpha1.LabelCronOperationName] == "basic-cronop"
+					}),
+			)).
+			WithTeardown("DeleteCronOperation", funcs.AllOf(
+				funcs.DeleteResources(manifests, "cronoperation.yaml"),
+				funcs.ResourcesDeletedWithin(2*time.Minute, manifests, "cronoperation.yaml"),
+			)).
+			WithTeardown("DeletePrerequisites", funcs.AllOf(
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/*.yaml", metav1.DeletePropagationForeground),
+				funcs.ResourcesDeletedWithin(3*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}
+
+func TestWatchOperationResourceChanges(t *testing.T) {
+	var firstScheduleTime, secondScheduleTime, thirdScheduleTime time.Time
+
+	manifests := "test/e2e/manifests/ops/watchoperations/resource-changes"
+	environment.Test(t,
+		features.NewWithDescription(t.Name(), "Tests WatchOperation resource change detection, validating that it creates Operations when watched resources are created, updated, deleted, or when multiple resources match the selector.").
+			WithLabel(LabelArea, LabelAreaOps).
+			WithLabel(LabelSize, LabelSizeSmall).
+			WithLabel(config.LabelTestSuite, SuiteOps).
+			WithSetup("CreatePrerequisites", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
+				// Wait for function to be ready with capabilities before creating Operation
+				funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "setup/*.yaml", pkgv1.Healthy(), pkgv1.Active()),
+			)).
+			Assess("CreateWatchOperation", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "watchoperation.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "watchoperation.yaml"),
+			)).
+			Assess("WatchOperationEstablishesWatching", funcs.AllOf(
+				// Wait for WatchOperation to become ready (controller started) and actively watching
+				funcs.ResourcesHaveConditionWithin(60*time.Second, manifests, "watchoperation.yaml", xpv1.ReconcileSuccess(), v1alpha1.WatchActive()),
+			)).
+			Assess("CreateWatchedResource", funcs.AllOf(
+				funcs.ApplyResources(FieldManager, manifests, "test-configmap.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "test-configmap.yaml"),
+			)).
+			Assess("WatchOperationCreatesFirstOperation", funcs.AllOf(
+				// Capture the first lastScheduleTime when WatchOperation creates its first Operation
+				funcs.ResourcesHaveFieldValueWithin(60*time.Second, manifests, "watchoperation.yaml", "status.lastScheduleTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								firstScheduleTime = parsed
+								return true
+							}
+						}
+						return false
+					})),
+				// Verify watching resources count is updated
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "watchoperation.yaml", "status.watchingResources", int64(1)),
+			)).
+			Assess("FirstOperationSucceeds", funcs.AllOf(
+				// Verify the WatchOperation's lastSuccessfulTime gets updated when Operation completes
+				funcs.ResourcesHaveFieldValueWithin(90*time.Second, manifests, "watchoperation.yaml", "status.lastSuccessfulTime", funcs.Any),
+			)).
+			Assess("UpdateWatchedResource", funcs.AllOf(
+				// Update the ConfigMap to trigger another Operation
+				funcs.ApplyResources(FieldManager, manifests, "test-configmap-updated.yaml"),
+				// Wait for the update to be processed
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "test-configmap-updated.yaml", "data.testData", "I trigger WatchOperations again!"),
+			)).
+			Assess("WatchOperationDetectsUpdate", funcs.AllOf(
+				// Wait for WatchOperation to detect the ConfigMap update and create another Operation
+				// Verify the lastScheduleTime advances, proving a new Operation was created
+				funcs.ResourcesHaveFieldValueWithin(60*time.Second, manifests, "watchoperation.yaml", "status.lastScheduleTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								if parsed.After(firstScheduleTime) {
+									secondScheduleTime = parsed
+									return true
+								}
+							}
+						}
+						return false
+					})),
+			)).
+			Assess("CreateSecondWatchedResource", funcs.AllOf(
+				// Create a second ConfigMap that matches the label selector
+				funcs.ApplyResources(FieldManager, manifests, "test-configmap-second.yaml"),
+				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "test-configmap-second.yaml"),
+			)).
+			Assess("WatchOperationDetectsSecondResource", funcs.AllOf(
+				// Wait for WatchOperation to detect the second ConfigMap and create another Operation
+				// Verify the lastScheduleTime advances again, proving a third Operation was created
+				funcs.ResourcesHaveFieldValueWithin(60*time.Second, manifests, "watchoperation.yaml", "status.lastScheduleTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								if parsed.After(secondScheduleTime) {
+									thirdScheduleTime = parsed
+									return true
+								}
+							}
+						}
+						return false
+					})),
+				// Verify watching resources count is updated to 2
+				funcs.ResourcesHaveFieldValueWithin(30*time.Second, manifests, "watchoperation.yaml", "status.watchingResources", int64(2)),
+			)).
+			Assess("DeleteFirstWatchedResource", funcs.AllOf(
+				// Delete the first ConfigMap to trigger another Operation
+				funcs.DeleteResources(manifests, "test-configmap.yaml"),
+				funcs.ResourcesDeletedWithin(30*time.Second, manifests, "test-configmap.yaml"),
+			)).
+			Assess("WatchOperationDetectsDeletion", funcs.AllOf(
+				// Wait for WatchOperation to detect the ConfigMap deletion and create another Operation
+				// Verify the lastScheduleTime advances once more, proving a fourth Operation was created
+				funcs.ResourcesHaveFieldValueWithin(60*time.Second, manifests, "watchoperation.yaml", "status.lastScheduleTime",
+					funcs.FieldValueChecker(func(got any) bool {
+						if timeStr, ok := got.(string); ok {
+							if parsed, err := time.Parse(time.RFC3339, timeStr); err == nil {
+								return parsed.After(thirdScheduleTime)
+							}
+						}
+						return false
+					})),
+				// As a final validation, verify that we have the expected number of Operations
+				// We expect at least 4 Operations: create for first ConfigMap, update for first ConfigMap,
+				// create for second ConfigMap, and delete for first ConfigMap
+				funcs.ListedResourcesValidatedWithin(30*time.Second,
+					&v1alpha1.OperationList{},
+					4, // At least 4 Operations should exist
+					func(o k8s.Object) bool {
+						// Check if this Operation was created by our WatchOperation
+						return o.GetLabels()[v1alpha1.LabelWatchOperationName] == "basic-watchop"
+					}),
+			)).
+			WithTeardown("DeleteSecondWatchedResource", funcs.AllOf(
+				funcs.DeleteResources(manifests, "test-configmap-second.yaml"),
+				funcs.ResourcesDeletedWithin(30*time.Second, manifests, "test-configmap-second.yaml"),
+			)).
+			WithTeardown("DeleteWatchOperation", funcs.AllOf(
+				funcs.DeleteResources(manifests, "watchoperation.yaml"),
+				funcs.ResourcesDeletedWithin(2*time.Minute, manifests, "watchoperation.yaml"),
+			)).
+			WithTeardown("DeletePrerequisites", funcs.AllOf(
+				funcs.DeleteResourcesWithPropagationPolicy(manifests, "setup/*.yaml", metav1.DeletePropagationForeground),
+				funcs.ResourcesDeletedWithin(3*time.Minute, manifests, "setup/*.yaml"),
+			)).
+			Feature(),
+	)
+}