Repository: crossplane/crossplane

Issue:
[v2] Add MRAP type and controller
<!--
Thank you for helping to improve Crossplane!

Please be sure to search for open issues before raising a new one. We use issues
for bug reports and feature requests. Please find us at https://slack.crossplane.io
for questions, support, and discussion.
-->

### What problem are you facing?
<!--
Please tell us a little about your use case - it's okay if it's hypothetical!
Leading with this context helps frame the feature request so we can ensure we
implement it sensibly.
--->

MRD design: https://github.com/crossplane/crossplane/blob/f5f6aac/design/one-pager-mrds.md

### How could Crossplane help solve your problem?
<!--
Let us know how you think Crossplane could help with your use case. 
-->

Per the design:

* Add a new MRAP type and controller
* The controller should aggregate all MRAPs, match MRDs, and toggle them to active
* The controller should never toggle MRDs to inactive (that's not allowed)
* Allow packaging MRAPs in a Configuration
* Create a default MRAP that enables `*` (all MRDs) at install time (in the initializer code)

Hints from issue comments:

Solution patch:
diff --git a/apis/apiextensions/v2alpha1/conditions.go b/apis/apiextensions/v2alpha1/conditions.go
index d3ca9e712..febb54bcd 100644
--- a/apis/apiextensions/v2alpha1/conditions.go
+++ b/apis/apiextensions/v2alpha1/conditions.go
@@ -28,14 +28,21 @@ const (
 	// A TypeEstablished XRD has created the CRD for its composite resource and
 	// started a controller to reconcile instances of said resource.
 	TypeEstablished xpv1.ConditionType = "Established"
+
+	// 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 (
-	EstablishedManagedResource xpv1.ConditionReason = "EstablishedManagedResource"
-	ReasonPendingManaged       xpv1.ConditionReason = "PendingManagedResource"
-	ReasonInactiveManaged      xpv1.ConditionReason = "InactiveManagedResource"
-	ReasonTerminatingManaged   xpv1.ConditionReason = "TerminatingManagedResource"
+	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"
 )
 
 // EstablishedManaged indicates that Crossplane has defined new kind of managed resource.
@@ -80,6 +87,49 @@ func BlockedManaged() xpv1.Condition {
 	}
 }
 
+// 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:             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,
+	}
+}
+
+// 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:               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 {
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 0a84a265c..1e03bf755 100644
--- a/apis/apiextensions/v2alpha1/mrd_types.go
+++ b/apis/apiextensions/v2alpha1/mrd_types.go
@@ -33,6 +33,7 @@ type ManagedResourceDefinitionSpec struct {
 	// State toggles whether the underlying CRD is created or not.
 	// +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"`
 }
 
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 d3c67f4b2..d9c9d707d 100644
--- a/cluster/crds/apiextensions.crossplane.io_managedresourcedefinitions.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_managedresourcedefinitions.yaml
@@ -267,6 +267,9 @@ spec:
                 - 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.
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 1ec0b5c67..b77c5c8dc 100644
--- a/internal/controller/apiextensions/apiextensions.go
+++ b/internal/controller/apiextensions/apiextensions.go
@@ -20,6 +20,7 @@ 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"
@@ -46,5 +47,9 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
 		return err
 	}
 
+	if err := activationpolicy.Setup(mgr, o); err != nil {
+		return err
+	}
+
 	return offered.Setup(mgr, o)
 }



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/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/managed/resources/custom_resource_definition_test.go b/internal/controller/apiextensions/managed/resources/custom_resource_definition_test.go
index 255b92180..a3e856bd5 100644
--- a/internal/controller/apiextensions/managed/resources/custom_resource_definition_test.go
+++ b/internal/controller/apiextensions/managed/resources/custom_resource_definition_test.go
@@ -114,7 +114,6 @@ func TestMergeCustomResourceDefinitionInto(t *testing.T) {
 			Raw: []byte(`{"type": "object", "properties": {"spec": {"type": "object"}}}`),
 		},
 	}
-
 	invalidSchema := &v2alpha1.CustomResourceValidation{
 		OpenAPIV3Schema: runtime.RawExtension{
 			Raw: []byte(`{invalid json`),
diff --git a/test/e2e/apiextensions_activation_policy_test.go b/test/e2e/apiextensions_activation_policy_test.go
new file mode 100644
index 000000000..0ceb59457
--- /dev/null
+++ b/test/e2e/apiextensions_activation_policy_test.go
@@ -0,0 +1,247 @@
+/*
+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 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, LabelAreaAPIExtensions).
+			WithLabel(LabelStage, LabelStageAlpha).
+			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, LabelAreaAPIExtensions).
+			WithLabel(LabelStage, LabelStageAlpha).
+			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, LabelAreaAPIExtensions).
+			WithLabel(LabelStage, LabelStageAlpha).
+			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
index 33da81a64..54ba72389 100644
--- a/test/e2e/apiextensions_mrds_test.go
+++ b/test/e2e/apiextensions_mrds_test.go
@@ -69,6 +69,12 @@ func TestMRDValidation(t *testing.T) {
 			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()).
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..87f3fc553
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/multiple-mraps/setup/mrd.yaml
@@ -0,0 +1,33 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: storages.multiple.activation-e2e.crossplane.io
+spec:
+  group: multiple.activation-e2e.crossplane.io
+  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..f2b4c90be
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/single-activation/setup/mrd.yaml
@@ -0,0 +1,33 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: buckets.single.activation-e2e.crossplane.io
+spec:
+  group: single.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
+              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..3be6389de
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-bucket.yaml
@@ -0,0 +1,30 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: buckets.wildcard.activation-e2e.crossplane.io
+spec:
+  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..89b64f104
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-database.yaml
@@ -0,0 +1,33 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: databases.wildcard.activation-e2e.crossplane.io
+spec:
+  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..039503bf1
--- /dev/null
+++ b/test/e2e/manifests/apiextensions/activation-policy/wildcard-activation/setup/mrd-instance.yaml
@@ -0,0 +1,33 @@
+apiVersion: apiextensions.crossplane.io/v2alpha1
+kind: ManagedResourceDefinition
+metadata:
+  name: instances.wildcard.activation-e2e.crossplane.io
+spec:
+  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/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