Repository: crossplane/crossplane

Issue:
Report `observedGeneration` in all core Crossplane controllers
<!--
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.
--->

All KRM resources have a `metadata.generation` field, [described](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata) as:

> a sequence number representing a specific generation of the desired state. Set by the system and monotonically increasing, per-resource.

All  Crossplane resources have a `Synced` status condition and at least one other - usually `Ready`. I wrote at length about what those conditions mean in https://github.com/crossplane/crossplane/issues/5643.

The status conditions are derived from a specific generation of the desired state. Actually, all of a resource's status (observed state) is set by a controller based on a specific generation of the desired state.

Today it's impossible to tell which generation of desired state Crossplane used to produce the observed state (status, conditions). This means you can't tell whether a controller has seen and acted upon the latest generation of desired state.

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

I think we should report the observed generation - probably on status conditions as shown in the [Kubernetes API conventions](https://github.com/kubernetes/community/blob/d06ec6a1a0ba9c0f5f2993e14e570544a7cbe6e2/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties).

There's some prior art for this:

* https://github.com/crossplane/crossplane-runtime/pull/633
* https://github.com/crossplane/crossplane-runtime/pull/789

The first PR adds observed generation to `status` for all MRs, and to all status conditions. To date I don't think any controller actually sets these fields though - so they're in the schema but unused.

The second implements observed generation for _managed resources_ (i.e. Crossplane providers). I'd like to do that for our core controllers - i.e. the ones in this repo.

I'm now questioning whether we really need `status.observedGeneration`. Given the meaning of our `Synced` condition, I think its observed generation could represent the latest generation the controller "synced" (i.e. reconciled).



Discussion:
@negz: See also https://github.com/crossplane/crossplane/issues/6174#issuecomment-2597253920.@n3wscott: I need to update `xpkg.upbound.io/crossplane-contrib/provider-nop` to use the latest upstream. @n3wscott: I missed `Composition`. Reopening to track the work.@n3wscott: `Composition` my bad, `Composition` does not have a status.

Solution patch:
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 31e12c5e7..99a8b9c82 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -14,7 +14,6 @@
   // PLEASE UPDATE THIS WHEN RELEASING.
   baseBranches: [
     'main',
-    'release-1.17',
     'release-1.18',
     'release-1.19',
     'release-1.20',
diff --git a/README.md b/README.md
index a2ccb94a7..04d9b6169 100644
--- a/README.md
+++ b/README.md
@@ -24,12 +24,12 @@ documentation].
 
 | Release | Release Date  |   EOL    |
 |:-------:|:-------------:|:--------:|
-|  v1.17  | Aug 29, 2024  | May 2025 |
 |  v1.18  | Nov 5, 2024   | Aug 2025 |
 |  v1.19  | Feb 11, 2025  | Nov 2025 |
-|  v1.20  | Early May '25 | Feb 2026 |
+|  v1.20  | May 21, 2025  | Feb 2026 |
 |  v1.21  | Early Aug '25 | May 2026 |
 |  v1.22  | Early Nov '25 | Aug 2026 |
+|  v1.23  | Early Feb '26 | Nov 2026 |
 
 You can subscribe to the [community calendar] to track all release dates, and
 find the most recent releases on the [releases] page.
diff --git a/apis/apiextensions/v1/xrd_types.go b/apis/apiextensions/v1/xrd_types.go
index 7135e1c09..daa8f8eca 100644
--- a/apis/apiextensions/v1/xrd_types.go
+++ b/apis/apiextensions/v1/xrd_types.go
@@ -230,6 +230,18 @@ type CompositeResourceDefinition struct {
 	Status CompositeResourceDefinitionStatus `json:"status,omitempty"`
 }
 
+// SetConditions delegates to Status.SetConditions.
+// Implements Conditioned.SetConditions.
+func (c *CompositeResourceDefinition) SetConditions(cs ...xpv1.Condition) {
+	c.Status.SetConditions(cs...)
+}
+
+// GetCondition delegates to Status.GetCondition.
+// Implements Conditioned.GetCondition.
+func (c *CompositeResourceDefinition) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return c.Status.GetCondition(ct)
+}
+
 // +kubebuilder:object:root=true
 
 // CompositeResourceDefinitionList contains a list of CompositeResourceDefinitions.
diff --git a/apis/apiextensions/v1alpha1/zz_generated.usage_types.go b/apis/apiextensions/v1alpha1/zz_generated.usage_types.go
index 27d4cc12a..10e84941d 100644
--- a/apis/apiextensions/v1alpha1/zz_generated.usage_types.go
+++ b/apis/apiextensions/v1alpha1/zz_generated.usage_types.go
@@ -104,6 +104,18 @@ type Usage struct {
 	Status UsageStatus `json:"status,omitempty"`
 }
 
+// SetConditions delegates to Status.SetConditions.
+// Implements Conditioned.SetConditions.
+func (u *Usage) SetConditions(c ...xpv1.Condition) {
+	u.Status.SetConditions(c...)
+}
+
+// GetCondition delegates to Status.GetCondition.
+// Implements Conditioned.GetCondition.
+func (u *Usage) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return u.Status.GetCondition(ct)
+}
+
 // +kubebuilder:object:root=true
 
 // UsageList contains a list of Usage.
diff --git a/apis/apiextensions/v1beta1/usage_types.go b/apis/apiextensions/v1beta1/usage_types.go
index 3195be1c7..366e305b5 100644
--- a/apis/apiextensions/v1beta1/usage_types.go
+++ b/apis/apiextensions/v1beta1/usage_types.go
@@ -101,6 +101,18 @@ type Usage struct {
 	Status UsageStatus `json:"status,omitempty"`
 }
 
+// SetConditions delegates to Status.SetConditions.
+// Implements Conditioned.SetConditions.
+func (u *Usage) SetConditions(c ...xpv1.Condition) {
+	u.Status.SetConditions(c...)
+}
+
+// GetCondition delegates to Status.GetCondition.
+// Implements Conditioned.GetCondition.
+func (u *Usage) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
+	return u.Status.GetCondition(ct)
+}
+
 // +kubebuilder:object:root=true
 
 // UsageList contains a list of Usage.
diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go
index f8d2812ab..d13ba38a5 100644
--- a/cmd/crossplane/core/core.go
+++ b/cmd/crossplane/core/core.go
@@ -96,7 +96,7 @@ type startCommand struct {
 	CABundlePath   string `env:"CA_BUNDLE_PATH"            help:"Additional CA bundle to use when fetching packages from registry."`
 	UserAgent      string `default:"${default_user_agent}" env:"USER_AGENT"                                                         help:"The User-Agent header that will be set on all package requests."`
 
-	XpkgCacheDir string `default:"/cache/xpkg" env:"XPKG_CACHE_DIR" help:"Directory used for caching package images."`
+	XpkgCacheDir string `aliases:"cache-dir" default:"/cache/xpkg" env:"XPKG_CACHE_DIR,CACHE_DIR" help:"Directory used for caching package images." short:"c"`
 
 	PackageRuntime string `default:"Deployment" env:"PACKAGE_RUNTIME" help:"The package runtime to use for packages with a runtime (e.g. Providers and Functions)"`
 
@@ -105,8 +105,7 @@ type startCommand struct {
 	MaxReconcileRate                 int           `default:"100" help:"The global maximum rate per second at which resources may checked for drift from the desired state."`
 	MaxConcurrentPackageEstablishers int           `default:"10"  help:"The the maximum number of goroutines to use for establishing Providers, Configurations and Functions."`
 
-	WebhookEnabled bool `default:"true" env:"WEBHOOK_ENABLED" hidden:""`
-	EnableWebhooks bool `default:"true" env:"ENABLE_WEBHOOKS" help:"Enable webhook configuration."`
+	EnableWebhooks bool `aliases:"webhook-enabled" default:"true" env:"ENABLE_WEBHOOKS,WEBHOOK_ENABLED" help:"Enable webhook configuration."`
 
 	WebhookPort     int `default:"9443" env:"WEBHOOK_PORT"      help:"The port the webhook server listens on."`
 	MetricsPort     int `default:"8080" env:"METRICS_PORT"      help:"The port the metrics server listens on."`
@@ -145,9 +144,6 @@ type startCommand struct {
 	// explicit and informative error on startup, instead of a potentially
 	// surprising one later.
 	EnableEnvironmentConfigs bool `hidden:""`
-
-	// Deprecated: Use XpkgCacheDir. Kept for backward compatibility.
-	CacheDir string `env:"CACHE_DIR" hidden:"" short:"c"`
 }
 
 // Run core Crossplane controllers.
@@ -238,14 +234,6 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 	if !c.EnableCompositionFunctionsExtraResources {
 		log.Info("Extra Resources are GA and cannot be disabled. The --enable-composition-functions-extra-resources flag will be removed in a future release.")
 	}
-	if !c.WebhookEnabled {
-		log.Info("The --webhook-enabled flag is deprecated, it is replaced with --enable-webhooks and will be removed in a future release.")
-	}
-
-	if c.CacheDir != "" {
-		log.Info("The --cache-dir flag is deprecated and will be removed in a future release. Use --xpkg-cache-dir instead.")
-		c.XpkgCacheDir = c.CacheDir
-	}
 
 	// TODO(negz): Include a link to a migration guide.
 	if c.EnableEnvironmentConfigs {
@@ -500,7 +488,7 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 
 	// Registering webhooks with the manager is what actually starts the webhook
 	// server.
-	if c.WebhookEnabled || c.EnableWebhooks {
+	if c.EnableWebhooks {
 		// TODO(muvaf): Once the implementation of other webhook handlers are
 		// fleshed out, implement a registration pattern similar to scheme
 		// registrations.
@@ -537,7 +525,7 @@ func (c *startCommand) SetupProbes(mgr ctrl.Manager) error {
 	}
 
 	// Add probes waiting for the webhook server if webhooks are enabled
-	if c.WebhookEnabled || c.EnableWebhooks {
+	if c.EnableWebhooks {
 		hookServer := mgr.GetWebhookServer()
 		if err := mgr.AddReadyzCheck("webhook", hookServer.StartedChecker()); err != nil {
 			return errors.Wrap(err, "cannot create webhook ready check")
diff --git a/internal/controller/apiextensions/claim/reconciler.go b/internal/controller/apiextensions/claim/reconciler.go
index 29594af6a..594a6251c 100644
--- a/internal/controller/apiextensions/claim/reconciler.go
+++ b/internal/controller/apiextensions/claim/reconciler.go
@@ -31,6 +31,7 @@ import (
 	"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"
@@ -188,8 +189,9 @@ type Reconciler struct {
 	composite crComposite
 	claim     crClaim
 
-	log    logging.Logger
-	record event.Recorder
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
 }
 
 type crComposite struct {
@@ -289,6 +291,7 @@ func NewReconciler(c client.Client, of resource.CompositeClaimKind, with resourc
 		claim:         defaultCRClaim(c),
 		log:           logging.NewNopLogger(),
 		record:        event.NewNopRecorder(),
+		conditions:    conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, ro := range o {
@@ -313,6 +316,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGetClaim, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetClaim)
 	}
+	status := r.conditions.For(cm)
 
 	record := r.record.WithAnnotations("external-name", meta.GetExternalName(cm))
 	log = log.WithValues(
@@ -325,7 +329,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// logging, publishing an event and updating the Synced status condition.
 	if meta.IsPaused(cm) {
 		r.record.Event(cm, event.Normal(reasonPaused, reconcilePausedMsg))
-		cm.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+		status.MarkConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
 		// If the pause annotation is removed, we will have a chance to
 		// reconcile again and resume and if status update fails, we will
 		// reconcile again to retry to update the status.
@@ -340,7 +344,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		if err := r.client.Get(ctx, types.NamespacedName{Name: ref.Name}, xr); resource.IgnoreNotFound(err) != nil {
 			err = errors.Wrap(err, errGetComposite)
 			record.Event(cm, event.Warning(reasonBind, err))
-			cm.SetConditions(xpv1.ReconcileError(err))
+			status.MarkConditions(xpv1.ReconcileError(err))
 			return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 		}
 	}
@@ -359,7 +363,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	if ref := xr.GetClaimReference(); meta.WasCreated(xr) && ref != nil && !cmp.Equal(cm.GetReference(), ref) {
 		err := errors.Errorf(errFmtUnbound, xr.GetName(), ref.Name)
 		record.Event(cm, event.Warning(reasonBind, err))
-		cm.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 
@@ -374,14 +378,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errUpgradeManagedFields)
 		record.Event(cm, event.Warning(reasonBind, err))
-		cm.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 
 	if meta.WasDeleted(cm) {
 		log = log.WithValues("deletion-timestamp", cm.GetDeletionTimestamp())
 
-		cm.SetConditions(xpv1.Deleting())
+		status.MarkConditions(xpv1.Deleting())
 		if meta.WasCreated(xr) {
 			requiresForegroundDeletion := false
 			if cdp := cm.GetCompositeDeletePolicy(); cdp != nil && *cdp == xpv1.CompositeDeleteForeground {
@@ -398,7 +402,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			if err := r.client.Delete(ctx, xr, do); resource.IgnoreNotFound(err) != nil {
 				err = errors.Wrap(err, errDeleteComposite)
 				record.Event(cm, event.Warning(reasonDelete, err))
-				cm.SetConditions(xpv1.ReconcileError(err))
+				status.MarkConditions(xpv1.ReconcileError(err))
 				return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 			}
 			if requiresForegroundDeletion {
@@ -413,7 +417,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		if err := r.claim.UnpublishConnection(ctx, cm, nil); err != nil {
 			err = errors.Wrap(err, errDeleteCDs)
 			record.Event(cm, event.Warning(reasonDelete, err))
-			cm.SetConditions(xpv1.ReconcileError(err))
+			status.MarkConditions(xpv1.ReconcileError(err))
 			return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 		}
 
@@ -422,12 +426,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		if err := r.claim.RemoveFinalizer(ctx, cm); err != nil {
 			err = errors.Wrap(err, errRemoveFinalizer)
 			record.Event(cm, event.Warning(reasonDelete, err))
-			cm.SetConditions(xpv1.ReconcileError(err))
+			status.MarkConditions(xpv1.ReconcileError(err))
 			return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 		}
 
 		log.Debug("Successfully deleted claim")
-		cm.SetConditions(xpv1.ReconcileSuccess())
+		status.MarkConditions(xpv1.ReconcileSuccess())
 		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 
@@ -437,7 +441,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errAddFinalizer)
 		record.Event(cm, event.Warning(reasonBind, err))
-		cm.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 
@@ -451,7 +455,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errSync)
 		record.Event(cm, event.Warning(reasonBind, err))
-		cm.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 
@@ -460,12 +464,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		record.Event(cm, event.Normal(reasonBind, "Successfully bound composite resource"))
 	}
 
-	cm.SetConditions(xpv1.ReconcileSuccess())
+	status.MarkConditions(xpv1.ReconcileSuccess())
 
 	// Copy any custom status conditions from the XR to the claim.
 	for _, cType := range xr.GetClaimConditionTypes() {
 		c := xr.GetCondition(cType)
-		cm.SetConditions(c)
+		status.MarkConditions(c)
 	}
 
 	if !resource.IsConditionTrue(xr.GetCondition(xpv1.TypeReady)) {
@@ -473,7 +477,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 		// We should be watching the composite resource and will have a
 		// request queued if it changes, so no need to requeue.
-		cm.SetConditions(Waiting())
+		status.MarkConditions(Waiting())
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 
@@ -481,7 +485,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	if err != nil {
 		err = errors.Wrap(err, errPropagateCDs)
 		record.Event(cm, event.Warning(reasonPropagate, err))
-		cm.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 	}
 	if propagated {
@@ -491,7 +495,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	// We have a watch on both the claim and its composite, so there's no
 	// need to requeue here.
-	cm.SetConditions(xpv1.Available())
+	status.MarkConditions(xpv1.Available())
 	return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, cm), errUpdateClaimStatus)
 }
 
diff --git a/internal/controller/apiextensions/composite/reconciler.go b/internal/controller/apiextensions/composite/reconciler.go
index 8aba73c61..94ebd71e0 100644
--- a/internal/controller/apiextensions/composite/reconciler.go
+++ b/internal/controller/apiextensions/composite/reconciler.go
@@ -27,6 +27,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -34,6 +35,7 @@ import (
 	"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/feature"
@@ -42,7 +44,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
@@ -425,8 +426,9 @@ func NewReconciler(c, uc client.Client, of resource.CompositeKind, opts ...Recon
 		// Dynamic watches are disabled by default.
 		engine: &NopWatchStarter{},
 
-		log:    logging.NewNopLogger(),
-		record: event.NewNopRecorder(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, f := range opts {
@@ -453,8 +455,9 @@ type Reconciler struct {
 	engine         WatchStarter
 	watchHandler   handler.EventHandler
 
-	log    logging.Logger
-	record event.Recorder
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
 
 	pollInterval time.Duration
 }
@@ -472,6 +475,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGet, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGet)
 	}
+	status := r.conditions.For(xr)
 
 	log = log.WithValues(
 		"uid", xr.GetUID(),
@@ -483,7 +487,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// after logging, publishing an event and updating the SYNC status condition
 	if meta.IsPaused(xr) {
 		r.record.Event(xr, event.Normal(reasonPaused, "Reconciliation is paused via the pause annotation"))
-		xr.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+		status.MarkConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
 		// If the pause annotation is removed, we will have a chance to reconcile again and resume
 		// and if status update fails, we will reconcile again to retry to update the status
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
@@ -492,11 +496,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	if meta.WasDeleted(xr) {
 		log = log.WithValues("deletion-timestamp", xr.GetDeletionTimestamp())
 
-		xr.SetConditions(xpv1.Deleting())
+		status.MarkConditions(xpv1.Deleting())
 		if err := r.composite.UnpublishConnection(ctx, xr, nil); err != nil {
 			err = errors.Wrap(err, errUnpublish)
 			r.record.Event(xr, event.Warning(reasonDelete, err))
-			xr.SetConditions(xpv1.ReconcileError(err))
+			status.MarkConditions(xpv1.ReconcileError(err))
 			return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 		}
 
@@ -506,12 +510,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			}
 			err = errors.Wrap(err, errRemoveFinalizer)
 			r.record.Event(xr, event.Warning(reasonDelete, err))
-			xr.SetConditions(xpv1.ReconcileError(err))
+			status.MarkConditions(xpv1.ReconcileError(err))
 			return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 		}
 
 		log.Debug("Successfully deleted composite resource")
-		xr.SetConditions(xpv1.ReconcileSuccess())
+		status.MarkConditions(xpv1.ReconcileSuccess())
 		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 
@@ -521,7 +525,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errAddFinalizer)
 		r.record.Event(xr, event.Warning(reasonInit, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 
@@ -532,7 +536,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errSelectComp)
 		r.record.Event(xr, event.Warning(reasonResolve, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 	if compRef := xr.GetCompositionReference(); compRef != nil && (orig == nil || *compRef != *orig) {
@@ -549,7 +553,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errFetchComp, "error", err)
 		err = errors.Wrap(err, errFetchComp)
 		r.record.Event(xr, event.Warning(reasonCompose, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 	if rev := xr.GetCompositionRevisionReference(); rev != nil && (origRev == nil || *rev != *origRev) {
@@ -560,7 +564,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errValidate, "error", err)
 		err = errors.Wrap(err, errValidate)
 		r.record.Event(xr, event.Warning(reasonCompose, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 
@@ -571,7 +575,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errConfigure)
 		r.record.Event(xr, event.Warning(reasonCompose, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 
@@ -594,7 +598,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			// point to the event.
 			err = errors.Wrap(errors.New(errInvalidResources), errCompose)
 		}
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 
 		meta := r.handleCommonCompositionResult(ctx, res, xr)
 		// We encountered a fatal error. For any custom status conditions that were
@@ -607,7 +611,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 				c.Status = corev1.ConditionUnknown
 				c.Reason = reasonFatalError
 				c.Message = "A fatal error occurred before the status of this condition could be determined."
-				xr.SetConditions(c)
+				status.MarkConditions(c)
 			}
 		}
 
@@ -616,7 +620,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	ws := make([]engine.Watch, len(xr.GetResourceReferences()))
 	for i, ref := range xr.GetResourceReferences() {
-		ws[i] = engine.WatchFor(composed.New(composed.FromReference(ref)), engine.WatchTypeComposedResource, r.watchHandler)
+		cr := &kunstructured.Unstructured{}
+		cr.SetGroupVersionKind(ref.GroupVersionKind())
+		ws[i] = engine.WatchFor(cr, engine.WatchTypeComposedResource, r.watchHandler)
 	}
 
 	// The ControllerEngine that starts this controller also starts a
@@ -624,7 +630,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	if err := r.engine.StartWatches(ctx, r.controllerName, ws...); err != nil {
 		err = errors.Wrap(err, errWatch)
 		r.record.Event(xr, event.Warning(reasonWatch, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 
@@ -636,7 +642,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 		err = errors.Wrap(err, errPublish)
 		r.record.Event(xr, event.Warning(reasonPublish, err))
-		xr.SetConditions(xpv1.ReconcileError(err))
+		status.MarkConditions(xpv1.ReconcileError(err))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, xr), errUpdateStatus)
 	}
 	if published {
@@ -697,7 +703,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 	}
 
-	xr.SetConditions(synced, ready)
+	status.MarkConditions(synced, ready)
 
 	// Requeue after the configured poll interval by default. If realtime
 	// compositions is enabled this'll be RequeueAfter: 0, i.e. no requeue.
diff --git a/internal/controller/apiextensions/definition/reconciler.go b/internal/controller/apiextensions/definition/reconciler.go
index e41a5fd3b..28fb23646 100644
--- a/internal/controller/apiextensions/definition/reconciler.go
+++ b/internal/controller/apiextensions/definition/reconciler.go
@@ -36,6 +36,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
 	"github.com/crossplane/crossplane-runtime/pkg/connection"
 	"github.com/crossplane/crossplane-runtime/pkg/controller"
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
@@ -254,8 +255,9 @@ func NewReconciler(ca resource.ClientApplicator, opts ...ReconcilerOption) *Reco
 
 		engine: &NopEngine{},
 
-		log:    logging.NewNopLogger(),
-		record: event.NewNopRecorder(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 
 		options: apiextensionscontroller.Options{
 			Options: controller.DefaultOptions(),
@@ -281,8 +283,9 @@ type Reconciler struct {
 
 	engine ControllerEngine
 
-	log    logging.Logger
-	record event.Recorder
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
 
 	options apiextensionscontroller.Options
 }
@@ -304,6 +307,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGetXRD, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetXRD)
 	}
+	status := r.conditions.For(d)
 
 	log = log.WithValues(
 		"uid", d.GetUID(),
@@ -319,7 +323,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	}
 
 	if meta.WasDeleted(d) {
-		d.Status.SetConditions(v1.TerminatingComposite())
+		status.MarkConditions(v1.TerminatingComposite())
 		if err := r.client.Status().Update(ctx, d); err != nil {
 			log.Debug(errUpdateStatus, "error", err)
 			if kerrors.IsConflict(err) {
@@ -470,7 +474,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	if r.engine.IsRunning(composite.ControllerName(d.GetName())) {
 		log.Debug("Composite resource controller is running")
-		d.Status.SetConditions(v1.WatchingComposite())
+		status.MarkConditions(v1.WatchingComposite())
 		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
 	}
 
@@ -531,7 +535,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	log.Debug("Started composite resource controller")
 
 	d.Status.Controllers.CompositeResourceTypeRef = v1.TypeReferenceTo(d.GetCompositeGroupVersionKind())
-	d.Status.SetConditions(v1.WatchingComposite())
+	status.MarkConditions(v1.WatchingComposite())
 	return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
 }
 
diff --git a/internal/controller/apiextensions/offered/reconciler.go b/internal/controller/apiextensions/offered/reconciler.go
index 5985b4f4b..11ed847fc 100644
--- a/internal/controller/apiextensions/offered/reconciler.go
+++ b/internal/controller/apiextensions/offered/reconciler.go
@@ -35,6 +35,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
+	"github.com/crossplane/crossplane-runtime/pkg/conditions"
 	"github.com/crossplane/crossplane-runtime/pkg/connection"
 	"github.com/crossplane/crossplane-runtime/pkg/controller"
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
@@ -227,8 +228,9 @@ func NewReconciler(ca resource.ClientApplicator, opts ...ReconcilerOption) *Reco
 
 		engine: &NopEngine{},
 
-		log:    logging.NewNopLogger(),
-		record: event.NewNopRecorder(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 
 		options: apiextensionscontroller.Options{
 			Options: controller.DefaultOptions(),
@@ -258,8 +260,9 @@ type Reconciler struct {
 
 	engine ControllerEngine
 
-	log    logging.Logger
-	record event.Recorder
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
 
 	options apiextensionscontroller.Options
 }
@@ -278,6 +281,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGetXRD, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetXRD)
 	}
+	status := r.conditions.For(d)
 
 	log = log.WithValues(
 		"uid", d.GetUID(),
@@ -293,7 +297,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	}
 
 	if meta.WasDeleted(d) {
-		d.Status.SetConditions(v1.TerminatingClaim())
+		status.MarkConditions(v1.TerminatingClaim())
 		if err := r.client.Status().Update(ctx, d); err != nil {
 			if kerrors.IsConflict(err) {
 				return reconcile.Result{Requeue: true}, nil
@@ -472,7 +476,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	if r.engine.IsRunning(claim.ControllerName(d.GetName())) {
 		log.Debug("Composite resource claim controller is running")
-		d.Status.SetConditions(v1.WatchingClaim())
+		status.MarkConditions(v1.WatchingClaim())
 		return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
 	}
 
@@ -508,6 +512,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	}
 
 	d.Status.Controllers.CompositeResourceClaimTypeRef = v1.TypeReferenceTo(d.GetClaimGroupVersionKind())
-	d.Status.SetConditions(v1.WatchingClaim())
+	status.MarkConditions(v1.WatchingClaim())
 	return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, d), errUpdateStatus)
 }
diff --git a/internal/controller/apiextensions/usage/reconciler.go b/internal/controller/apiextensions/usage/reconciler.go
index 715cdbcfc..8a7e118d0 100644
--- a/internal/controller/apiextensions/usage/reconciler.go
+++ b/internal/controller/apiextensions/usage/reconciler.go
@@ -35,6 +35,7 @@ import (
 	"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"
@@ -191,8 +192,9 @@ func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 			selectorResolver: newAPISelectorResolver(kube),
 		},
 
-		log:    logging.NewNopLogger(),
-		record: event.NewNopRecorder(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, f := range opts {
@@ -207,8 +209,9 @@ type Reconciler struct {
 
 	usage usageResource
 
-	log    logging.Logger
-	record event.Recorder
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
 
 	pollInterval time.Duration
 }
@@ -229,8 +232,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	// Validate APIVersion of used object provided as input.
 	// We parse this value while indexing the objects, and we need to make sure it is valid.
-	_, err := schema.ParseGroupVersion(u.Spec.Of.APIVersion)
-	if err != nil {
+	if _, err := schema.ParseGroupVersion(u.Spec.Of.APIVersion); err != nil {
 		return reconcile.Result{}, errors.Wrap(err, errParseAPIVersion)
 	}
 
@@ -245,6 +247,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	of := u.Spec.Of
 	by := u.Spec.By
+	status := r.conditions.For(u)
 
 	// Identify used xp composed as an unstructured object.
 	used := composed.New(composed.FromReference(v1.ObjectReference{
@@ -272,15 +275,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 				APIVersion: by.APIVersion,
 			}))
 			// Get the using resource
-			err := r.client.Get(ctx, client.ObjectKey{Name: by.ResourceRef.Name}, using)
-			if xpresource.IgnoreNotFound(err) != nil {
+			if err := r.client.Get(ctx, client.ObjectKey{Name: by.ResourceRef.Name}, using); xpresource.IgnoreNotFound(err) != nil {
 				log.Debug(errGetUsing, "error", err)
 				err = errors.Wrap(xpresource.IgnoreNotFound(err), errGetUsing)
 				r.record.Event(u, event.Warning(reasonGetUsing, err))
 				return reconcile.Result{}, err
-			}
-
-			if err == nil {
+			} else if err == nil {
 				// Using resource is still there, so we need to wait for it to be deleted.
 				msg := fmt.Sprintf("Waiting for the using resource (which is a %q named %q) to be deleted.", by.Kind, by.ResourceRef.Name)
 				log.Debug(msg)
@@ -300,17 +300,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// So, we can proceed with the deletion of the usage.
 
 		// Get the used resource
-		var err error
-		if err = r.client.Get(ctx, client.ObjectKey{Name: of.ResourceRef.Name}, used); xpresource.IgnoreNotFound(err) != nil {
+		if err := r.client.Get(ctx, client.ObjectKey{Name: of.ResourceRef.Name}, used); xpresource.IgnoreNotFound(err) != nil {
 			log.Debug(errGetUsed, "error", err)
 			err = errors.Wrap(err, errGetUsed)
 			r.record.Event(u, event.Warning(reasonGetUsed, err))
 			return reconcile.Result{}, err
-		}
-
-		// Remove the in-use label from the used resource if no other usages
-		// exists.
-		if err == nil {
+		} else if err == nil {
+			// Remove the in-use label from the used resource if no other usages exists.
 			usageList := &v1beta1.UsageList{}
 			if err = r.client.List(ctx, usageList, client.MatchingFields{usage.InUseIndexKey: usage.IndexValueForObject(used.GetUnstructured())}); err != nil {
 				log.Debug(errListUsages, "error", err)
@@ -345,7 +341,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 					// very soon.
 					time.Sleep(2 * time.Second)
 					log.Info("Replaying deletion of the used resource", "apiVersion", used.GetAPIVersion(), "kind", used.GetKind(), "name", used.GetName(), "policy", policy)
-					if err = r.client.Delete(context.Background(), used, client.PropagationPolicy(policy)); err != nil {
+					if err := r.client.Delete(context.Background(), used, client.PropagationPolicy(policy)); err != nil {
 						log.Info("Error when replaying deletion of the used resource", "apiVersion", used.GetAPIVersion(), "kind", used.GetKind(), "name", used.GetName(), "err", err)
 					}
 				}()
@@ -353,7 +349,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 
 		// Remove the finalizer from the usage
-		if err = r.usage.RemoveFinalizer(ctx, u); err != nil {
+		if err := r.usage.RemoveFinalizer(ctx, u); err != nil {
 			log.Debug(errRemoveFinalizer, "error", err)
 			if kerrors.IsConflict(err) {
 				return reconcile.Result{Requeue: true}, nil
@@ -451,7 +447,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 	}
 
-	u.Status.SetConditions(xpv1.Available())
+	status.MarkConditions(xpv1.Available())
 
 	// We are only watching the Usage itself but not using or used resources.
 	// So, we need to reconcile the Usage periodically to check if the using
diff --git a/internal/controller/pkg/manager/reconciler.go b/internal/controller/pkg/manager/reconciler.go
index 542ce77bd..49355344d 100644
--- a/internal/controller/pkg/manager/reconciler.go
+++ b/internal/controller/pkg/manager/reconciler.go
@@ -36,6 +36,7 @@ import (
 	"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"
@@ -152,11 +153,12 @@ func WithRecorder(er event.Recorder) ReconcilerOption {
 
 // Reconciler reconciles packages.
 type Reconciler struct {
-	client resource.ClientApplicator
-	pkg    Revisioner
-	config xpkg.ConfigStore
-	log    logging.Logger
-	record event.Recorder
+	client     resource.ClientApplicator
+	pkg        Revisioner
+	config     xpkg.ConfigStore
+	log        logging.Logger
+	record     event.Recorder
+	conditions conditions.Manager
 
 	newPackage             func() v1.Package
 	newPackageRevision     func() v1.PackageRevision
@@ -278,9 +280,10 @@ func NewReconciler(mgr ctrl.Manager, opts ...ReconcilerOption) *Reconciler {
 			Client:     mgr.GetClient(),
 			Applicator: resource.NewAPIPatchingApplicator(mgr.GetClient()),
 		},
-		pkg:    NewNopRevisioner(),
-		log:    logging.NewNopLogger(),
-		record: event.NewNopRecorder(),
+		pkg:        NewNopRevisioner(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, f := range opts {
@@ -305,12 +308,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGetPackage, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetPackage)
 	}
+	status := r.conditions.For(p)
 
 	// Check the pause annotation and return if it has the value "true"
 	// after logging, publishing an event and updating the SYNC status condition
 	if meta.IsPaused(p) {
 		r.record.Event(p, event.Normal(reasonPaused, reconcilePausedMsg))
-		p.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+		status.MarkConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
 		// If the pause annotation is removed, we will have a chance to reconcile again and resume
 		// and if status update fails, we will reconcile again to retry to update the status
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, p), errUpdateStatus)
@@ -358,7 +362,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	pullSecretConfig, pullSecretFromConfig, err := r.config.PullSecretFor(ctx, p.GetResolvedSource())
 	if err != nil {
 		err = errors.Wrap(err, errGetPullConfig)
-		p.SetConditions(v1.Unpacking().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unpacking().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, p)
 
 		r.record.Event(p, event.Warning(reasonImageConfig, err))
@@ -380,7 +384,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	revisionName, err := r.pkg.Revision(ctx, p, secrets...)
 	if err != nil {
 		err = errors.Wrap(err, errUnpack)
-		p.SetConditions(v1.Unpacking().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unpacking().WithMessage(err.Error()))
 		r.record.Event(p, event.Warning(reasonUnpack, err))
 
 		if updateErr := r.client.Status().Update(ctx, p); updateErr != nil {
@@ -391,7 +395,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	}
 
 	if revisionName == "" {
-		p.SetConditions(v1.Unpacking().WithMessage("Waiting for unpack to complete"))
+		status.MarkConditions(v1.Unpacking().WithMessage("Waiting for unpack to complete"))
 		r.record.Event(p, event.Normal(reasonUnpack, "Waiting for unpack to complete"))
 		return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, p), errUpdateStatus)
 	}
@@ -474,14 +478,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			// package is already healthy.
 			r.record.Event(p, event.Normal(reasonInstall, "Successfully installed package revision"))
 		}
-		p.SetConditions(v1.Healthy())
+		status.MarkConditions(v1.Healthy())
 	}
 	if prHealthy := pr.GetCondition(v1.TypeHealthy); prHealthy.Status == corev1.ConditionFalse {
-		p.SetConditions(v1.Unhealthy().WithMessage(prHealthy.Message))
+		status.MarkConditions(v1.Unhealthy().WithMessage(prHealthy.Message))
 		r.record.Event(p, event.Warning(reasonInstall, errors.New(errUnhealthyPackageRevision)))
 	}
 	if prHealthy := pr.GetCondition(v1.TypeHealthy); prHealthy.Status == corev1.ConditionUnknown {
-		p.SetConditions(v1.UnknownHealth().WithMessage(prHealthy.Message))
+		status.MarkConditions(v1.UnknownHealth().WithMessage(prHealthy.Message))
 		r.record.Event(p, event.Warning(reasonInstall, errors.New(errUnknownPackageRevisionHealth)))
 	}
 
@@ -547,11 +551,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 	}
 
-	p.SetConditions(v1.Active())
+	status.MarkConditions(v1.Active())
 
 	// If current revision is still not active, the package is inactive.
 	if pr.GetDesiredState() != v1.PackageRevisionActive {
-		p.SetConditions(v1.Inactive().WithMessage("Package is inactive"))
+		status.MarkConditions(v1.Inactive().WithMessage("Package is inactive"))
 	}
 
 	// NOTE(hasheddan): when the first package revision is created for a
diff --git a/internal/controller/pkg/resolver/reconciler.go b/internal/controller/pkg/resolver/reconciler.go
index 7825a13fc..95463f032 100644
--- a/internal/controller/pkg/resolver/reconciler.go
+++ b/internal/controller/pkg/resolver/reconciler.go
@@ -37,6 +37,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"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/feature"
 	"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
@@ -149,14 +150,15 @@ func WithDowngradesEnabled() ReconcilerOption {
 
 // Reconciler reconciles packages.
 type Reconciler struct {
-	client   client.Client
-	log      logging.Logger
-	lock     resource.Finalizer
-	newDag   internaldag.NewDAGFn
-	fetcher  xpkg.Fetcher
-	config   xpkg.ConfigStore
-	registry string
-	features *feature.Flags
+	client     client.Client
+	log        logging.Logger
+	lock       resource.Finalizer
+	newDag     internaldag.NewDAGFn
+	fetcher    xpkg.Fetcher
+	config     xpkg.ConfigStore
+	registry   string
+	features   *feature.Flags
+	conditions conditions.Manager
 
 	downgradesEnabled bool
 }
@@ -209,11 +211,12 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
 // NewReconciler creates a new lock dependency reconciler.
 func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
-		client:  mgr.GetClient(),
-		lock:    resource.NewAPIFinalizer(mgr.GetClient(), finalizer),
-		log:     logging.NewNopLogger(),
-		newDag:  internaldag.NewMapDag,
-		fetcher: xpkg.NewNopFetcher(),
+		client:     mgr.GetClient(),
+		lock:       resource.NewAPIFinalizer(mgr.GetClient(), finalizer),
+		log:        logging.NewNopLogger(),
+		newDag:     internaldag.NewMapDag,
+		fetcher:    xpkg.NewNopFetcher(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, f := range opts {
@@ -238,6 +241,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGetLock, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetLock)
 	}
+	status := r.conditions.For(lock)
 
 	// If no packages exist in Lock then we remove finalizer and wait until
 	// a package is added to reconcile again. This allows for cleanup of the
@@ -273,7 +277,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	implied, err := dag.Init(v1beta1.ToNodes(lock.Packages...))
 	if err != nil {
 		log.Debug(errBuildDAG, "error", err)
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errBuildDAG)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errBuildDAG)))
 		_ = r.client.Status().Update(ctx, lock)
 		return reconcile.Result{}, errors.Wrap(err, errBuildDAG)
 	}
@@ -283,13 +287,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	_, err = dag.Sort()
 	if err != nil {
 		log.Debug(errSortDAG, "error", err)
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errSortDAG)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errSortDAG)))
 		_ = r.client.Status().Update(ctx, lock)
 		return reconcile.Result{}, errors.Wrap(err, errSortDAG)
 	}
 
 	if len(implied) == 0 {
-		lock.SetConditions(v1beta1.ResolutionSucceeded())
+		status.MarkConditions(v1beta1.ResolutionSucceeded())
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus)
 	}
 
@@ -301,14 +305,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	depID := dep.Identifier()
 	if !ok {
 		log.Debug(errInvalidDependency, "error", errors.Errorf(errFmtMissingDependency, depID))
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtMissingDependency, depID)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtMissingDependency, depID)))
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus)
 	}
 
 	ref, err := name.ParseReference(depID, name.WithDefaultRegistry(r.registry))
 	if err != nil {
 		log.Debug(errInvalidDependency, "error", err)
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errInvalidDependency)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errInvalidDependency)))
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus)
 	}
 
@@ -318,14 +322,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		l, err := NewPackageList(dep)
 		if err != nil {
 			log.Debug(errGetDependency, "error", err)
-			lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errGetDependency)))
+			status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errGetDependency)))
 			_ = r.client.Status().Update(ctx, lock)
 			return reconcile.Result{}, errors.Wrap(err, errGetDependency)
 		}
 
 		if err := r.client.List(ctx, l); err != nil {
 			log.Debug(errGetDependency, "error", err)
-			lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errGetDependency)))
+			status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errGetDependency)))
 			_ = r.client.Status().Update(ctx, lock)
 			return reconcile.Result{}, errors.Wrap(err, errGetDependency)
 		}
@@ -352,7 +356,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		var addVer string
 		if addVer, err = r.findDependencyVersionToInstall(ctx, dep, log, ref); err != nil {
 			log.Debug(errFindDependency, "error", errors.Wrapf(err, depID, dep.Constraints))
-			lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependency)))
+			status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependency)))
 			_ = r.client.Status().Update(ctx, lock)
 			return reconcile.Result{Requeue: false}, errors.Wrap(err, errFindDependency)
 		}
@@ -361,14 +365,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// dictating constraints.
 		if addVer == "" {
 			log.Debug(errFindDependencyUpgrade, "error", errors.Errorf(errFmtNoValidVersion, depID, dep.Constraints))
-			lock.SetConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtNoValidVersion, depID, dep.Constraints)))
+			status.MarkConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtNoValidVersion, depID, dep.Constraints)))
 			return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus)
 		}
 
 		pack, err := NewPackage(dep, addVer, ref)
 		if err != nil {
 			log.Debug(errConstructDependency, "error", err)
-			lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errConstructDependency)))
+			status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errConstructDependency)))
 			_ = r.client.Status().Update(ctx, lock)
 			return reconcile.Result{}, errors.Wrap(err, errConstructDependency)
 		}
@@ -377,12 +381,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// it creates.
 		if err := r.client.Create(ctx, pack); err != nil && !kerrors.IsAlreadyExists(err) {
 			log.Debug(errCreateDependency, "error", err)
-			lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errCreateDependency)))
+			status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errCreateDependency)))
 			_ = r.client.Status().Update(ctx, lock)
 			return reconcile.Result{}, errors.Wrap(err, errCreateDependency)
 		}
 
-		lock.SetConditions(v1beta1.ResolutionSucceeded())
+		status.MarkConditions(v1beta1.ResolutionSucceeded())
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus)
 	}
 
@@ -397,7 +401,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	n, err := dag.GetNode(depID)
 	if err != nil {
 		log.Debug(errInvalidDependency, "error", errors.Errorf(errFmtMissingDependency, depID))
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtMissingDependency, depID)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtMissingDependency, depID)))
 		_ = r.client.Status().Update(ctx, lock)
 		return reconcile.Result{}, errors.Errorf(errFmtMissingDependency, depID)
 	}
@@ -405,7 +409,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	newVer, err := r.findDependencyVersionToUpdate(ctx, ref, installedVersion, n, log)
 	if err != nil {
 		log.Debug(errFindDependencyUpgrade, "error", errors.Wrapf(err, depID, dep.Constraints))
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependencyUpgrade)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependencyUpgrade)))
 		_ = r.client.Status().Update(ctx, lock)
 		return reconcile.Result{}, errors.Wrap(err, errFindDependencyUpgrade)
 	}
@@ -419,12 +423,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	if err := r.client.Update(ctx, pkg); err != nil {
 		log.Debug(errUpdateDependency, "error", err)
-		lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errUpdateDependency)))
+		status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errUpdateDependency)))
 		_ = r.client.Status().Update(ctx, lock)
 		return reconcile.Result{}, errors.Wrap(err, errUpdateDependency)
 	}
 
-	lock.SetConditions(v1beta1.ResolutionSucceeded())
+	status.MarkConditions(v1beta1.ResolutionSucceeded())
 	return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus)
 }
 
diff --git a/internal/controller/pkg/revision/dependency.go b/internal/controller/pkg/revision/dependency.go
index ef61ce240..b1501e1fb 100644
--- a/internal/controller/pkg/revision/dependency.go
+++ b/internal/controller/pkg/revision/dependency.go
@@ -31,6 +31,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
 
 	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
@@ -62,14 +64,16 @@ type PackageDependencyManager struct {
 	client      client.Client
 	newDag      dag.NewDAGFn
 	packageType schema.GroupVersionKind
+	log         logging.Logger
 }
 
 // NewPackageDependencyManager creates a new PackageDependencyManager.
-func NewPackageDependencyManager(c client.Client, nd dag.NewDAGFn, pkgType schema.GroupVersionKind) *PackageDependencyManager {
+func NewPackageDependencyManager(c client.Client, nd dag.NewDAGFn, pkgType schema.GroupVersionKind, l logging.Logger) *PackageDependencyManager {
 	return &PackageDependencyManager{
 		client:      c,
 		newDag:      nd,
 		packageType: pkgType,
+		log:         l,
 	}
 }
 
@@ -147,6 +151,12 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P
 	// to another registry)
 	for _, lp := range lock.Packages {
 		if self.Name == lp.Name && self.Type == lp.Type && self.Source != lp.Identifier() {
+			m.log.Debug("Package with same name and type but different source exists in lock. Removing it.",
+				"name", lp.Name,
+				"type", ptr.Deref(lp.Type, "Unknown"),
+				"old-source", lp.Identifier(),
+				"new-source", self.Source,
+			)
 			if err := m.RemoveSelf(ctx, pr); err != nil {
 				return found, installed, invalid, err
 			}
@@ -178,16 +188,16 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P
 
 		// If any direct dependencies are missing we skip checking for
 		// transitive ones.
-		var missing []string
+		var missing []dag.Node
 		for _, dep := range self.Dependencies {
 			if d.NodeExists(dep.Identifier()) {
 				installed++
 				continue
 			}
-			missing = append(missing, dep.Identifier())
+			missing = append(missing, &dep)
 		}
 		if installed != found {
-			return found, installed, invalid, errors.Errorf(errFmtMissingDependencies, missing)
+			return found, installed, invalid, errors.Errorf(errFmtMissingDependencies, NDependenciesAndSomeMore(3, missing))
 		}
 	}
 
@@ -198,15 +208,15 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P
 	found = len(tree)
 	installed = found
 	// Check if any dependencies or transitive dependencies are missing (implied).
-	var missing []string
+	var missing []dag.Node
 	for _, imp := range implied {
 		if _, ok := tree[imp.Identifier()]; ok {
 			installed--
-			missing = append(missing, imp.Identifier())
+			missing = append(missing, imp)
 		}
 	}
 	if len(missing) != 0 {
-		return found, installed, invalid, errors.Errorf(errFmtMissingDependencies, missing)
+		return found, installed, invalid, errors.Errorf(errFmtMissingDependencies, NDependenciesAndSomeMore(3, missing))
 	}
 
 	// All of our dependencies and transitive dependencies must exist. Check
@@ -269,9 +279,24 @@ func (m *PackageDependencyManager) RemoveSelf(ctx context.Context, pr v1.Package
 	// Find self and remove. If we don't exist, its a no-op.
 	for i, lp := range lock.Packages {
 		if lp.Name == pr.GetName() {
+			m.log.Debug("Removing package revision from lock", "name", lp.Name)
 			lock.Packages = append(lock.Packages[:i], lock.Packages[i+1:]...)
 			return m.client.Update(ctx, lock)
 		}
 	}
 	return nil
 }
+
+// NDependenciesAndSomeMore returns the first n dependencies in detail, and a
+// summary of how many more exist.
+func NDependenciesAndSomeMore(n int, d []dag.Node) string {
+	out := make([]string, len(d))
+	for i := range d {
+		if d[i].GetConstraints() == "" {
+			out[i] = fmt.Sprintf("%q", d[i].Identifier())
+			continue
+		}
+		out[i] = fmt.Sprintf("%q (%s)", d[i].Identifier(), d[i].GetConstraints())
+	}
+	return resource.StableNAndSomeMore(n, out)
+}
diff --git a/internal/controller/pkg/revision/reconciler.go b/internal/controller/pkg/revision/reconciler.go
index 39abe74a4..36c0436e8 100644
--- a/internal/controller/pkg/revision/reconciler.go
+++ b/internal/controller/pkg/revision/reconciler.go
@@ -32,11 +32,11 @@ import (
 	"k8s.io/client-go/kubernetes"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	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/feature"
@@ -277,6 +277,7 @@ type Reconciler struct {
 	config         xpkg.ConfigStore
 	log            logging.Logger
 	record         event.Recorder
+	conditions     conditions.Manager
 	features       *feature.Flags
 	namespace      string
 	serviceAccount string
@@ -315,14 +316,13 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error {
 		Owns(&corev1.Service{}).
 		Owns(&corev1.Secret{}).
 		Owns(&corev1.ServiceAccount{}).
-		Watches(&v1alpha1.ControllerConfig{}, &EnqueueRequestForReferencingProviderRevisions{
-			client: mgr.GetClient(),
-		}).
-		Watches(&v1beta1.ImageConfig{}, enqueueProviderRevisionsForImageConfig(mgr.GetClient(), log))
+		Watches(&v1beta1.Lock{}, EnqueuePackageRevisionsForLock(mgr.GetClient(), &v1.ProviderRevisionList{}, log)).
+		Watches(&v1alpha1.ControllerConfig{}, EnqueuePackageRevisionsForRuntimeConfig(mgr.GetClient(), &v1.ProviderRevisionList{}, log)).
+		Watches(&v1beta1.ImageConfig{}, EnqueuePackageRevisionsForImageConfig(mgr.GetClient(), &v1.ProviderRevisionList{}, log))
 
 	ro := []ReconcilerOption{
 		WithCache(o.Cache),
-		WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ProviderGroupVersionKind)),
+		WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ProviderGroupVersionKind, log)),
 		WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers)),
 		WithNewPackageRevisionFn(nr),
 		WithParser(parser.New(metaScheme, objScheme)),
@@ -340,9 +340,7 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error {
 		ro = append(ro, WithRuntimeHooks(NewProviderHooks(mgr.GetClient(), o.DefaultRegistry)))
 
 		if o.Features.Enabled(features.EnableBetaDeploymentRuntimeConfigs) {
-			cb = cb.Watches(&v1beta1.DeploymentRuntimeConfig{}, &EnqueueRequestForReferencingProviderRevisions{
-				client: mgr.GetClient(),
-			})
+			cb = cb.Watches(&v1beta1.DeploymentRuntimeConfig{}, EnqueuePackageRevisionsForRuntimeConfig(mgr.GetClient(), &v1.ProviderRevisionList{}, log))
 		}
 	}
 
@@ -376,7 +374,7 @@ func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error {
 	log := o.Logger.WithValues("controller", name)
 	r := NewReconciler(mgr,
 		WithCache(o.Cache),
-		WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ConfigurationGroupVersionKind)),
+		WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ConfigurationGroupVersionKind, log)),
 		WithNewPackageRevisionFn(nr),
 		WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers)),
 		WithParser(parser.New(metaScheme, objScheme)),
@@ -393,7 +391,8 @@ func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error {
 	return ctrl.NewControllerManagedBy(mgr).
 		Named(name).
 		For(&v1.ConfigurationRevision{}).
-		Watches(&v1beta1.ImageConfig{}, enqueueConfigurationRevisionsForImageConfig(mgr.GetClient(), log)).
+		Watches(&v1beta1.Lock{}, EnqueuePackageRevisionsForLock(mgr.GetClient(), &v1.ConfigurationRevisionList{}, log)).
+		Watches(&v1beta1.ImageConfig{}, EnqueuePackageRevisionsForImageConfig(mgr.GetClient(), &v1.ConfigurationRevisionList{}, log)).
 		WithOptions(o.ForControllerRuntime()).
 		Complete(ratelimiter.NewReconciler(name, errors.WithSilentRequeueOnConflict(r), o.GlobalRateLimiter))
 }
@@ -429,14 +428,13 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error {
 		Owns(&corev1.Service{}).
 		Owns(&corev1.Secret{}).
 		Owns(&corev1.ServiceAccount{}).
-		Watches(&v1alpha1.ControllerConfig{}, &EnqueueRequestForReferencingFunctionRevisions{
-			client: mgr.GetClient(),
-		}).
-		Watches(&v1beta1.ImageConfig{}, enqueueFunctionRevisionsForImageConfig(mgr.GetClient(), log))
+		Watches(&v1beta1.Lock{}, EnqueuePackageRevisionsForLock(mgr.GetClient(), &v1.FunctionRevisionList{}, log)).
+		Watches(&v1alpha1.ControllerConfig{}, EnqueuePackageRevisionsForRuntimeConfig(mgr.GetClient(), &v1.FunctionRevisionList{}, log)).
+		Watches(&v1beta1.ImageConfig{}, EnqueuePackageRevisionsForImageConfig(mgr.GetClient(), &v1.FunctionRevisionList{}, log))
 
 	ro := []ReconcilerOption{
 		WithCache(o.Cache),
-		WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.FunctionGroupVersionKind)),
+		WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.FunctionGroupVersionKind, log)),
 		WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers)),
 		WithNewPackageRevisionFn(nr),
 		WithParser(parser.New(metaScheme, objScheme)),
@@ -454,9 +452,7 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error {
 		ro = append(ro, WithRuntimeHooks(NewFunctionHooks(mgr.GetClient(), o.DefaultRegistry)))
 
 		if o.Features.Enabled(features.EnableBetaDeploymentRuntimeConfigs) {
-			cb = cb.Watches(&v1beta1.DeploymentRuntimeConfig{}, &EnqueueRequestForReferencingFunctionRevisions{
-				client: mgr.GetClient(),
-			})
+			cb = cb.Watches(&v1beta1.DeploymentRuntimeConfig{}, EnqueuePackageRevisionsForRuntimeConfig(mgr.GetClient(), &v1.FunctionRevisionList{}, log))
 		}
 	}
 
@@ -467,15 +463,16 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error {
 // NewReconciler creates a new package revision reconciler.
 func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
-		client:    mgr.GetClient(),
-		cache:     xpkg.NewNopCache(),
-		revision:  resource.NewAPIFinalizer(mgr.GetClient(), finalizer),
-		objects:   NewNopEstablisher(),
-		parser:    parser.New(nil, nil),
-		linter:    parser.NewPackageLinter(nil, nil, nil),
-		versioner: version.New(),
-		log:       logging.NewNopLogger(),
-		record:    event.NewNopRecorder(),
+		client:     mgr.GetClient(),
+		cache:      xpkg.NewNopCache(),
+		revision:   resource.NewAPIFinalizer(mgr.GetClient(), finalizer),
+		objects:    NewNopEstablisher(),
+		parser:     parser.New(nil, nil),
+		linter:     parser.NewPackageLinter(nil, nil, nil),
+		versioner:  version.New(),
+		log:        logging.NewNopLogger(),
+		record:     event.NewNopRecorder(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, f := range opts {
@@ -500,6 +497,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		log.Debug(errGetPackageRevision, "error", err)
 		return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetPackageRevision)
 	}
+	status := r.conditions.For(pr)
 
 	log = log.WithValues(
 		"uid", pr.GetUID(),
@@ -511,7 +509,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// after logging, publishing an event and updating the SYNC status condition
 	if meta.IsPaused(pr) {
 		r.record.Event(pr, event.Normal(reasonPaused, reconcilePausedMsg))
-		pr.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
+		status.MarkConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg))
 		// If the pause annotation is removed, we will have a chance to reconcile again and resume
 		// and if status update fails, we will reconcile again to retry to update the status
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus)
@@ -600,7 +598,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			// Initialize the installed condition if they are not already set to
 			// communicate the status of the package.
 			if pr.GetCondition(v1.TypeHealthy).Status == corev1.ConditionUnknown {
-				pr.SetConditions(v1.AwaitingVerification())
+				status.MarkConditions(v1.AwaitingVerification())
 				return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update status with awaiting verification")
 			}
 			return reconcile.Result{}, nil
@@ -619,7 +617,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	pullSecretConfig, pullSecretFromConfig, err := r.config.PullSecretFor(ctx, pr.GetResolvedSource())
 	if err != nil {
 		err = errors.Wrap(err, errGetPullConfig)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonImageConfig, err))
@@ -635,7 +633,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			log.Debug(errManifestBuilderOptions, "error", err)
 
 			err = errors.Wrap(err, errManifestBuilderOptions)
-			pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+			status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 			_ = r.client.Status().Update(ctx, pr)
 
 			r.record.Event(pr, event.Warning(reasonSync, err))
@@ -687,7 +685,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 				// package revision is already healthy.
 				r.record.Event(pr, event.Normal(reasonSync, "Successfully configured package revision"))
 			}
-			pr.SetConditions(v1.Healthy())
+			status.MarkConditions(v1.Healthy())
 			return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus)
 		}
 	}
@@ -732,7 +730,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// an error.
 	if rc == nil && pullPolicyNever {
 		err := errors.New(errPullPolicyNever)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonParse, err))
@@ -762,7 +760,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		imgrc, err := r.backend.Init(ctx, bo...)
 		if err != nil {
 			err = errors.Wrap(err, errInitParserBackend)
-			pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+			status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 			_ = r.client.Status().Update(ctx, pr)
 
 			r.record.Event(pr, event.Warning(reasonParse, err))
@@ -805,7 +803,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	}
 	if err != nil {
 		err = errors.Wrap(err, errParsePackage)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonParse, err))
@@ -815,7 +813,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// Lint package using package-specific linter.
 	if err := r.linter.Lint(pkg); err != nil {
 		err = errors.Wrap(err, errLintPackage)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonLint, err))
@@ -832,7 +830,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// we check here to avoid a potential panic on 0 index below.
 	if len(pkg.GetMeta()) != 1 {
 		err = errors.New(errNotOneMeta)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonLint, err))
@@ -850,7 +848,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 
 		err = errors.Wrap(err, errUpdateMeta)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonSync, err))
@@ -862,7 +860,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	if pr.GetIgnoreCrossplaneConstraints() == nil || !*pr.GetIgnoreCrossplaneConstraints() {
 		if err := xpkg.PackageCrossplaneCompatible(r.versioner)(pkgMeta); err != nil {
 			err = errors.Wrap(err, errIncompatible)
-			pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+			status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 
 			r.record.Event(pr, event.Warning(reasonLint, err))
 
@@ -885,7 +883,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			}
 
 			err = errors.Wrap(err, errResolveDeps)
-			pr.SetConditions(v1.UnknownHealth().WithMessage(err.Error()))
+			status.MarkConditions(v1.UnknownHealth().WithMessage(err.Error()))
 			_ = r.client.Status().Update(ctx, pr)
 
 			r.record.Event(pr, event.Warning(reasonDependencies, err))
@@ -901,7 +899,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			}
 
 			err = errors.Wrap(err, errPreHook)
-			pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+			status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 			_ = r.client.Status().Update(ctx, pr)
 
 			r.record.Event(pr, event.Warning(reasonSync, err))
@@ -918,7 +916,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 
 		err = errors.Wrap(err, errEstablishControl)
-		pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+		status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 		_ = r.client.Status().Update(ctx, pr)
 
 		r.record.Event(pr, event.Warning(reasonSync, err))
@@ -950,7 +948,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 			}
 
 			err = errors.Wrap(err, errPostHook)
-			pr.SetConditions(v1.Unhealthy().WithMessage(err.Error()))
+			status.MarkConditions(v1.Unhealthy().WithMessage(err.Error()))
 			_ = r.client.Status().Update(ctx, pr)
 
 			r.record.Event(pr, event.Warning(reasonSync, err))
@@ -964,7 +962,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// package revision is already healthy.
 		r.record.Event(pr, event.Normal(reasonSync, "Successfully configured package revision"))
 	}
-	pr.SetConditions(v1.Healthy())
+	status.MarkConditions(v1.Healthy())
 	return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus)
 }
 
@@ -1036,96 +1034,3 @@ func (r *Reconciler) runtimeManifestBuilderOptions(ctx context.Context, pwr v1.P
 
 	return opts, nil
 }
-
-func enqueueProviderRevisionsForImageConfig(kube client.Client, log logging.Logger) handler.EventHandler {
-	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
-		ic, ok := o.(*v1beta1.ImageConfig)
-		if !ok {
-			return nil
-		}
-		// We only care about ImageConfigs that have a pull secret.
-		if ic.Spec.Registry == nil || ic.Spec.Registry.Authentication == nil || ic.Spec.Registry.Authentication.PullSecretRef.Name == "" {
-			return nil
-		}
-		// Enqueue all ProviderRevision matching the prefixes in the ImageConfig.
-		l := &v1.ProviderRevisionList{}
-		if err := kube.List(ctx, l); err != nil {
-			// Nothing we can do, except logging, if we can't list ProviderRevisions.
-			log.Debug("Cannot list provider revisions while attempting to enqueue from ImageConfig", "error", err)
-			return nil
-		}
-
-		var matches []reconcile.Request
-		for _, p := range l.Items {
-			for _, m := range ic.Spec.MatchImages {
-				if strings.HasPrefix(p.GetSource(), m.Prefix) || strings.HasPrefix(p.GetResolvedSource(), m.Prefix) {
-					log.Debug("Enqueuing provider revision for image config", "providerRevision", p.Name, "imageConfig", ic.Name)
-					matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: p.Name}})
-				}
-			}
-		}
-		return matches
-	})
-}
-
-func enqueueConfigurationRevisionsForImageConfig(kube client.Client, log logging.Logger) handler.EventHandler {
-	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
-		ic, ok := o.(*v1beta1.ImageConfig)
-		if !ok {
-			return nil
-		}
-		// We only care about ImageConfigs that have a pull secret.
-		if ic.Spec.Registry == nil || ic.Spec.Registry.Authentication == nil || ic.Spec.Registry.Authentication.PullSecretRef.Name == "" {
-			return nil
-		}
-		// Enqueue all ConfigurationRevision matching the prefixes in the ImageConfig.
-		l := &v1.ConfigurationRevisionList{}
-		if err := kube.List(ctx, l); err != nil {
-			// Nothing we can do, except logging, if we can't list ConfigurationRevisions.
-			log.Debug("Cannot list configuration revisions while attempting to enqueue from ImageConfig", "error", err)
-			return nil
-		}
-
-		var matches []reconcile.Request
-		for _, p := range l.Items {
-			for _, m := range ic.Spec.MatchImages {
-				if strings.HasPrefix(p.GetSource(), m.Prefix) || strings.HasPrefix(p.GetResolvedSource(), m.Prefix) {
-					log.Debug("Enqueuing configuration revision for image config", "configurationRevision", p.Name, "imageConfig", ic.Name)
-					matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: p.Name}})
-				}
-			}
-		}
-		return matches
-	})
-}
-
-func enqueueFunctionRevisionsForImageConfig(kube client.Client, log logging.Logger) handler.EventHandler {
-	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
-		ic, ok := o.(*v1beta1.ImageConfig)
-		if !ok {
-			return nil
-		}
-		// We only care about ImageConfigs that have a pull secret.
-		if ic.Spec.Registry == nil || ic.Spec.Registry.Authentication == nil || ic.Spec.Registry.Authentication.PullSecretRef.Name == "" {
-			return nil
-		}
-		// Enqueue all FunctionRevision matching the prefixes in the ImageConfig.
-		l := &v1.FunctionRevisionList{}
-		if err := kube.List(ctx, l); err != nil {
-			// Nothing we can do, except logging, if we can't list FunctionRevisions.
-			log.Debug("Cannot list function revisions while attempting to enqueue from ImageConfig", "error", err)
-			return nil
-		}
-
-		var matches []reconcile.Request
-		for _, p := range l.Items {
-			for _, m := range ic.Spec.MatchImages {
-				if strings.HasPrefix(p.GetSource(), m.Prefix) || strings.HasPrefix(p.GetResolvedSource(), m.Prefix) {
-					log.Debug("Enqueuing function revision for image config", "functionRevision", p.Name, "imageConfig", ic.Name)
-					matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: p.Name}})
-				}
-			}
-		}
-		return matches
-	})
-}
diff --git a/internal/controller/pkg/revision/watch.go b/internal/controller/pkg/revision/watch.go
index 1110e804d..65d9af985 100644
--- a/internal/controller/pkg/revision/watch.go
+++ b/internal/controller/pkg/revision/watch.go
@@ -18,143 +18,118 @@ package revision
 
 import (
 	"context"
+	"fmt"
+	"strings"
 
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/util/workqueue"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
+
 	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
 	"github.com/crossplane/crossplane/apis/pkg/v1alpha1"
 	"github.com/crossplane/crossplane/apis/pkg/v1beta1"
 )
 
-type adder interface {
-	Add(item reconcile.Request)
-}
-
-// EnqueueRequestForReferencingProviderRevisions enqueues a request for all
-// provider revisions that reference a ControllerConfig when the given
-// ControllerConfig changes.
-type EnqueueRequestForReferencingProviderRevisions struct {
-	client client.Client
-}
-
-// Create enqueues a request for all provider revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingProviderRevisions) Create(ctx context.Context, evt event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.Object, q)
-}
-
-// Update enqueues a request for all provider revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingProviderRevisions) Update(ctx context.Context, evt event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.ObjectOld, q)
-	e.add(ctx, evt.ObjectNew, q)
-}
+// EnqueuePackageRevisionsForRuntimeConfig enqueues a reconcile for all package
+// revisions that use a ControllerConfig or DeploymentRuntimeConfig.
+func EnqueuePackageRevisionsForRuntimeConfig(kube client.Client, l v1.PackageRevisionList, log logging.Logger) handler.EventHandler {
+	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
+		cc, isCC := o.(*v1alpha1.ControllerConfig)
+		rc, isRC := o.(*v1beta1.DeploymentRuntimeConfig)
 
-// Delete enqueues a request for all provider revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingProviderRevisions) Delete(ctx context.Context, evt event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.Object, q)
-}
+		if !isCC && !isRC {
+			return nil
+		}
 
-// Generic enqueues a request for all provider revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingProviderRevisions) Generic(ctx context.Context, evt event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.Object, q)
-}
+		rl := l.DeepCopyObject().(v1.PackageRevisionList) //nolint:forcetypeassert // Guaranteed to be PackageRevisionList.
+		if err := kube.List(ctx, rl); err != nil {
+			log.Debug("Cannot list package revisions while attempting to enqueue from runtime config", "error", err)
+			return nil
+		}
 
-func (e *EnqueueRequestForReferencingProviderRevisions) add(ctx context.Context, obj runtime.Object, queue adder) {
-	cc, isCC := obj.(*v1alpha1.ControllerConfig)
-	rc, isRC := obj.(*v1beta1.DeploymentRuntimeConfig)
-
-	if !isCC && !isRC {
-		return
-	}
-
-	l := &v1.ProviderRevisionList{}
-	if err := e.client.List(ctx, l); err != nil {
-		// TODO(hasheddan): Handle this error?
-		return
-	}
-
-	for _, pr := range l.Items {
-		if isCC {
-			ref := pr.GetControllerConfigRef()
-			if ref != nil && ref.Name == cc.GetName() {
-				queue.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: pr.GetName()}})
+		var matches []reconcile.Request
+		for _, rev := range rl.GetRevisions() {
+			rt, ok := rev.(v1.PackageRevisionWithRuntime)
+			if !ok {
+				continue
 			}
-		}
-		if isRC {
-			ref := pr.GetRuntimeConfigRef()
-			if ref != nil && ref.Name == rc.GetName() {
-				queue.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: pr.GetName()}})
+			if isCC {
+				ref := rt.GetControllerConfigRef()
+				if ref != nil && ref.Name == cc.GetName() {
+					matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: rev.GetName()}})
+				}
+			}
+			if isRC {
+				ref := rt.GetRuntimeConfigRef()
+				if ref != nil && ref.Name == rc.GetName() {
+					matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: rev.GetName()}})
+				}
 			}
 		}
-	}
-}
-
-// EnqueueRequestForReferencingFunctionRevisions enqueues a request for all
-// function revisions that reference a ControllerConfig when the given
-// ControllerConfig changes.
-type EnqueueRequestForReferencingFunctionRevisions struct {
-	client client.Client
-}
 
-// Create enqueues a request for all function revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingFunctionRevisions) Create(ctx context.Context, evt event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.Object, q)
+		return matches
+	})
 }
 
-// Update enqueues a request for all function revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingFunctionRevisions) Update(ctx context.Context, evt event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.ObjectOld, q)
-	e.add(ctx, evt.ObjectNew, q)
-}
+// EnqueuePackageRevisionsForImageConfig enqueues a reconcile for all package
+// revisions an ImageConfig applies to.
+func EnqueuePackageRevisionsForImageConfig(kube client.Client, l v1.PackageRevisionList, log logging.Logger) handler.EventHandler {
+	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
+		ic, ok := o.(*v1beta1.ImageConfig)
+		if !ok {
+			return nil
+		}
+		// We only care about ImageConfigs that have a pull secret.
+		if ic.Spec.Registry == nil || ic.Spec.Registry.Authentication == nil || ic.Spec.Registry.Authentication.PullSecretRef.Name == "" {
+			return nil
+		}
+		// Enqueue all package revisions matching the prefixes in the ImageConfig.
+		rl := l.DeepCopyObject().(v1.PackageRevisionList) //nolint:forcetypeassert // Guaranteed to be PackageRevisionList.
+		if err := kube.List(ctx, rl); err != nil {
+			// Nothing we can do, except logging, if we can't list
+			// package revisions.
+			log.Debug("Cannot list package revisions while attempting to enqueue from ImageConfig", "error", err)
+			return nil
+		}
 
-// Delete enqueues a request for all function revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingFunctionRevisions) Delete(ctx context.Context, evt event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.Object, q)
+		var matches []reconcile.Request
+		for _, rev := range rl.GetRevisions() {
+			for _, m := range ic.Spec.MatchImages {
+				if strings.HasPrefix(rev.GetSource(), m.Prefix) || strings.HasPrefix(rev.GetResolvedSource(), m.Prefix) {
+					log.Debug("Enqueuing for image config",
+						"revision-type", fmt.Sprintf("%T", rev),
+						"revision-name", rev.GetName(),
+						"image-config-name", ic.GetName())
+					matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: rev.GetName()}})
+				}
+			}
+		}
+		return matches
+	})
 }
 
-// Generic enqueues a request for all function revisions that reference a given
-// ControllerConfig.
-func (e *EnqueueRequestForReferencingFunctionRevisions) Generic(ctx context.Context, evt event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-	e.add(ctx, evt.Object, q)
-}
+// EnqueuePackageRevisionsForLock enqueues a reconcile for all package
+// revisions when the Lock changes.
+func EnqueuePackageRevisionsForLock(kube client.Client, l v1.PackageRevisionList, log logging.Logger) handler.EventHandler {
+	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
+		if _, ok := o.(*v1beta1.Lock); !ok {
+			return nil
+		}
 
-func (e *EnqueueRequestForReferencingFunctionRevisions) add(ctx context.Context, obj runtime.Object, queue adder) {
-	cc, isCC := obj.(*v1alpha1.ControllerConfig)
-	rc, isRC := obj.(*v1beta1.DeploymentRuntimeConfig)
-
-	if !isCC && !isRC {
-		return
-	}
-
-	l := &v1.FunctionRevisionList{}
-	if err := e.client.List(ctx, l); err != nil {
-		// TODO(hasheddan): Handle this error?
-		return
-	}
-
-	for _, pr := range l.Items {
-		if isCC {
-			ref := pr.GetControllerConfigRef()
-			if ref != nil && ref.Name == cc.GetName() {
-				queue.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: pr.GetName()}})
-			}
+		rl := l.DeepCopyObject().(v1.PackageRevisionList) //nolint:forcetypeassert // Guaranteed to be PackageRevisionList.
+		if err := kube.List(ctx, rl); err != nil {
+			// Nothing we can do, except logging, if we can't list FunctionRevisions.
+			log.Debug("Cannot list package revisions while attempting to enqueue from Lock", "error", err)
+			return nil
 		}
-		if isRC {
-			ref := pr.GetRuntimeConfigRef()
-			if ref != nil && ref.Name == rc.GetName() {
-				queue.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: pr.GetName()}})
-			}
+
+		var matches []reconcile.Request
+		for _, rev := range rl.GetRevisions() {
+			matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: rev.GetName()}})
 		}
-	}
+		return matches
+	})
 }
diff --git a/internal/controller/pkg/signature/reconciler.go b/internal/controller/pkg/signature/reconciler.go
index 678a9de9a..5d3181758 100644
--- a/internal/controller/pkg/signature/reconciler.go
+++ b/internal/controller/pkg/signature/reconciler.go
@@ -32,6 +32,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"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/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
@@ -118,6 +119,7 @@ type Reconciler struct {
 	serviceAccount string
 	namespace      string
 	registry       string
+	conditions     conditions.Manager
 
 	newRevision func() v1.PackageRevision
 }
@@ -230,8 +232,9 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error {
 // NewReconciler creates a new package reconciler for signature verification.
 func NewReconciler(client client.Client, opts ...ReconcilerOption) *Reconciler {
 	r := &Reconciler{
-		client: client,
-		log:    logging.NewNopLogger(),
+		client:     client,
+		log:        logging.NewNopLogger(),
+		conditions: conditions.ObservedGenerationPropagationManager{},
 	}
 
 	for _, f := range opts {
@@ -256,10 +259,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		}
 
 		log.Debug(errGetRevision, "error", err)
-		pr.SetConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetRevision)))
+		status := r.conditions.For(pr)
+		status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetRevision)))
 		_ = r.client.Status().Update(ctx, pr)
 		return reconcile.Result{}, errors.Wrap(err, errGetRevision)
 	}
+	status := r.conditions.For(pr)
 
 	log = log.WithValues(
 		"uid", pr.GetUID(),
@@ -293,7 +298,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	ic, vc, err := r.config.ImageVerificationConfigFor(ctx, imagePath)
 	if err != nil {
 		log.Debug("Cannot get image verification config", "error", err)
-		pr.SetConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetVerificationConfig)))
+		status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetVerificationConfig)))
 		_ = r.client.Status().Update(ctx, pr)
 		return reconcile.Result{}, errors.Wrap(err, errGetVerificationConfig)
 	}
@@ -301,7 +306,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// No verification config found for this image, so, we will skip
 		// verification.
 		log.Debug("No signature verification config found for image, skipping verification")
-		pr.SetConditions(v1.VerificationSkipped())
+		status.MarkConditions(v1.VerificationSkipped())
 		pr.ClearAppliedImageConfigRef(v1.ImageConfigReasonVerify)
 		return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update package status")
 	}
@@ -314,7 +319,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	ref, err := name.ParseReference(imagePath, name.WithDefaultRegistry(r.registry))
 	if err != nil {
 		log.Debug("Cannot parse package image reference", "error", err)
-		pr.SetConditions(v1.VerificationIncomplete(errors.Wrap(err, errParseReference)))
+		status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errParseReference)))
 		_ = r.client.Status().Update(ctx, pr)
 		return reconcile.Result{}, errors.Wrap(err, errParseReference)
 	}
@@ -327,7 +332,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	_, s, err := r.config.PullSecretFor(ctx, imagePath)
 	if err != nil {
 		log.Debug("Cannot get image config pull secret for image", "error", err)
-		pr.SetConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetConfigPullSecret)))
+		status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetConfigPullSecret)))
 		_ = r.client.Status().Update(ctx, pr)
 		return reconcile.Result{}, errors.Wrap(err, errGetConfigPullSecret)
 	}
@@ -337,14 +342,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 
 	if err = r.validator.Validate(ctx, ref, vc, pullSecrets...); err != nil {
 		log.Debug("Signature verification failed", "error", err)
-		pr.SetConditions(v1.VerificationFailed(ic, err))
+		status.MarkConditions(v1.VerificationFailed(ic, err))
 		if sErr := r.client.Status().Update(ctx, pr); sErr != nil {
 			return reconcile.Result{}, errors.Wrap(sErr, "cannot update status with failed verification")
 		}
 		return reconcile.Result{}, errors.Wrap(err, errFailedVerification)
 	}
 
-	pr.SetConditions(v1.VerificationSucceeded(ic))
+	status.MarkConditions(v1.VerificationSucceeded(ic))
 	return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update status with successful verification")
 }
 



Test patch:
diff --git a/internal/controller/apiextensions/composite/reconciler_test.go b/internal/controller/apiextensions/composite/reconciler_test.go
index 88195adc0..69a1511b7 100644
--- a/internal/controller/apiextensions/composite/reconciler_test.go
+++ b/internal/controller/apiextensions/composite/reconciler_test.go
@@ -27,6 +27,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/utils/ptr"
@@ -40,7 +41,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
-	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/reference"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
@@ -577,10 +577,9 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithWatchStarter("cool-controller", nil, WatchStarterFn(func(_ context.Context, _ string, ws ...engine.Watch) error {
-						cd := composed.New(composed.FromReference(corev1.ObjectReference{
-							APIVersion: "example.org/v1",
-							Kind:       "ComposedResource",
-						}))
+						cd := &kunstructured.Unstructured{}
+						cd.SetAPIVersion("example.org/v1")
+						cd.SetKind("ComposedResource")
 						want := []engine.Watch{engine.WatchFor(cd, engine.WatchTypeComposedResource, nil)}
 
 						if diff := cmp.Diff(want, ws, cmp.AllowUnexported(engine.Watch{})); diff != "" {
diff --git a/internal/controller/pkg/manager/reconciler_test.go b/internal/controller/pkg/manager/reconciler_test.go
index 6dcd5228f..ad5141a7f 100644
--- a/internal/controller/pkg/manager/reconciler_test.go
+++ b/internal/controller/pkg/manager/reconciler_test.go
@@ -32,6 +32,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	commonv1 "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"
@@ -89,7 +90,8 @@ func TestReconcile(t *testing.T) {
 					client: resource.ClientApplicator{
 						Client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
 					},
-					log: testLog,
+					log:        testLog,
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -105,7 +107,8 @@ func TestReconcile(t *testing.T) {
 					client: resource.ClientApplicator{
 						Client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
 					},
-					log: testLog,
+					log:        testLog,
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -125,8 +128,9 @@ func TestReconcile(t *testing.T) {
 							MockList: test.NewMockListFn(errBoom),
 						},
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -157,8 +161,9 @@ func TestReconcile(t *testing.T) {
 							return nil
 						}),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 					pkg: &MockRevisioner{
 						MockRevision: NewMockRevisionFn("", errBoom),
 					},
@@ -205,6 +210,7 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", errBoom),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -244,6 +250,7 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -298,8 +305,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("imageConfigName", "new/image/path", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -349,8 +357,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -402,8 +411,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -453,8 +463,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -515,8 +526,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -597,8 +609,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -660,8 +673,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -724,8 +738,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -825,8 +840,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -908,8 +924,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -959,8 +976,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
@@ -1009,8 +1027,9 @@ func TestReconcile(t *testing.T) {
 						MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil),
 						MockRewritePath:   fake.NewMockRewritePathFn("", "", nil),
 					},
-					log:    testLog,
-					record: event.NewNopRecorder(),
+					log:        testLog,
+					record:     event.NewNopRecorder(),
+					conditions: conditions.ObservedGenerationPropagationManager{},
 				},
 			},
 			want: want{
diff --git a/internal/controller/pkg/revision/dependency_test.go b/internal/controller/pkg/revision/dependency_test.go
index 1f3dd0384..052e96693 100644
--- a/internal/controller/pkg/revision/dependency_test.go
+++ b/internal/controller/pkg/revision/dependency_test.go
@@ -28,6 +28,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/crossplane/crossplane-runtime/pkg/errors"
+	"github.com/crossplane/crossplane-runtime/pkg/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
 	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
@@ -81,6 +82,7 @@ func TestResolve(t *testing.T) {
 					client: &test.MockClient{
 						MockGet: test.NewMockGetFn(errBoom),
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{},
 				pr: &v1.ConfigurationRevision{
@@ -101,6 +103,7 @@ func TestResolve(t *testing.T) {
 						MockGet:    test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
 						MockCreate: test.NewMockCreateFn(errBoom),
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{},
 				pr: &v1.ConfigurationRevision{
@@ -128,6 +131,7 @@ func TestResolve(t *testing.T) {
 							},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{},
 				pr: &v1.ConfigurationRevision{
@@ -166,6 +170,7 @@ func TestResolve(t *testing.T) {
 							},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{},
 				pr: &v1.ConfigurationRevision{
@@ -204,6 +209,7 @@ func TestResolve(t *testing.T) {
 							MockAddOrUpdateNodes: func(_ ...dag.Node) {},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{
 					Spec: pkgmetav1.ConfigurationSpec{
@@ -214,6 +220,7 @@ func TestResolve(t *testing.T) {
 								},
 								{
 									Provider: ptr.To("not-here-2"),
+									Version:  ">= v2.0.0",
 								},
 							},
 						},
@@ -231,7 +238,7 @@ func TestResolve(t *testing.T) {
 			},
 			want: want{
 				total: 2,
-				err:   errors.Errorf(errFmtMissingDependencies, []string{"not-here-1", "not-here-2"}),
+				err:   errors.Errorf(errFmtMissingDependencies, `"not-here-1", "not-here-2" (>= v2.0.0)`),
 			},
 		},
 		"ErrorSelfExistMissingDependencies": {
@@ -291,6 +298,7 @@ func TestResolve(t *testing.T) {
 							},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{
 					Spec: pkgmetav1.ConfigurationSpec{
@@ -319,7 +327,7 @@ func TestResolve(t *testing.T) {
 			want: want{
 				total:     3,
 				installed: 1,
-				err:       errors.Errorf(errFmtMissingDependencies, []string{"not-here-2", "not-here-3"}),
+				err:       errors.Errorf(errFmtMissingDependencies, `"not-here-2", "not-here-3"`),
 			},
 		},
 		"ErrorSelfExistInvalidDependencies": {
@@ -387,6 +395,7 @@ func TestResolve(t *testing.T) {
 							},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{
 					Spec: pkgmetav1.ConfigurationSpec{
@@ -501,6 +510,7 @@ func TestResolve(t *testing.T) {
 							},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{
 					Spec: pkgmetav1.ConfigurationSpec{
@@ -579,6 +589,7 @@ func TestResolve(t *testing.T) {
 							MockAddOrUpdateNodes: func(_ ...dag.Node) {},
 						}
 					},
+					log: logging.NewNopLogger(),
 				},
 				meta: &pkgmetav1.Configuration{},
 				pr: &v1.ConfigurationRevision{
diff --git a/internal/controller/pkg/revision/watch_test.go b/internal/controller/pkg/revision/watch_test.go
deleted file mode 100644
index a26d5b87f..000000000
--- a/internal/controller/pkg/revision/watch_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-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 revision
-
-import (
-	"context"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	"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/errors"
-	"github.com/crossplane/crossplane-runtime/pkg/test"
-
-	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
-	"github.com/crossplane/crossplane/apis/pkg/v1alpha1"
-)
-
-var _ handler.EventHandler = &EnqueueRequestForReferencingProviderRevisions{}
-
-type addFn func(item reconcile.Request)
-
-func (fn addFn) Add(item reconcile.Request) {
-	fn(item)
-}
-
-func TestAdd(t *testing.T) {
-	errBoom := errors.New("boom")
-	name := "coolname"
-	prName := "coolpr"
-
-	cases := map[string]struct {
-		ctx              context.Context
-		obj              runtime.Object
-		client           client.Client
-		controllerConfig *v1alpha1.ControllerConfig
-		queue            adder
-	}{
-		"ObjectIsNotAControllConfig": {
-			queue: addFn(func(_ reconcile.Request) { t.Errorf("queue.Add() called unexpectedly") }),
-		},
-		"ListError": {
-			obj: &v1alpha1.ControllerConfig{ObjectMeta: metav1.ObjectMeta{Name: name}},
-			client: &test.MockClient{
-				MockList: test.NewMockListFn(errBoom),
-			},
-			controllerConfig: &v1alpha1.ControllerConfig{},
-			queue:            addFn(func(_ reconcile.Request) { t.Errorf("queue.Add() called unexpectedly") }),
-		},
-		"SuccessfulEnqueue": {
-			obj: &v1alpha1.ControllerConfig{ObjectMeta: metav1.ObjectMeta{Name: name}},
-			client: &test.MockClient{
-				MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error {
-					l := obj.(*v1.ProviderRevisionList)
-					l.Items = []v1.ProviderRevision{
-						{
-							ObjectMeta: metav1.ObjectMeta{Name: prName},
-							Spec: v1.ProviderRevisionSpec{
-								PackageRevisionRuntimeSpec: v1.PackageRevisionRuntimeSpec{
-									PackageRuntimeSpec: v1.PackageRuntimeSpec{
-										ControllerConfigReference: &v1.ControllerConfigReference{},
-									},
-								},
-							},
-						},
-						{
-							ObjectMeta: metav1.ObjectMeta{Name: "noRef"},
-						},
-					}
-					return nil
-				}),
-			},
-			queue: addFn(func(got reconcile.Request) {
-				want := reconcile.Request{NamespacedName: types.NamespacedName{Name: prName}}
-				if diff := cmp.Diff(want, got); diff != "" {
-					t.Errorf("-want, +got:\n%s\n", diff)
-				}
-			}),
-		},
-	}
-
-	for _, tc := range cases {
-		e := &EnqueueRequestForReferencingProviderRevisions{client: tc.client}
-		e.add(tc.ctx, tc.obj, tc.queue)
-	}
-}
diff --git a/test/e2e/funcs/feature.go b/test/e2e/funcs/feature.go
index 5b0d4ef3d..7067fb921 100644
--- a/test/e2e/funcs/feature.go
+++ b/test/e2e/funcs/feature.go
@@ -348,6 +348,7 @@ func ResourceHasConditionWithin(d time.Duration, o k8s.Object, cds ...xpv1.Condi
 		desired := strings.Join(reasons, ", ")
 
 		t.Logf("Waiting %s for %s to become %s...", d, identifier(o), desired)
+		ogReport := make(map[string]bool)
 		old := make([]xpv1.Condition, len(cds))
 		match := func(o k8s.Object) bool {
 			u := asUnstructured(o)
@@ -355,10 +356,28 @@ func ResourceHasConditionWithin(d time.Duration, o k8s.Object, cds ...xpv1.Condi
 			_ = fieldpath.Pave(u.Object).GetValueInto("status", &s)
 
 			for i, want := range cds {
+				// Update the wanted observed generation to the latest object generation.
+				want.ObservedGeneration = u.GetGeneration()
+
 				got := s.GetCondition(want.Type)
+
+				// Until https://github.com/crossplane/crossplane/issues/6420 is resolved, crossplane will be in a
+				// transition period. A condition with an observedGeneration of zero means it is not yet being
+				// propagated when setting the conditions. To help that transition, we will move the generation forward
+				// ONLY if it is zero. But we will also log this fact to find it in the logs.
+				if got.ObservedGeneration == 0 {
+					got.ObservedGeneration = u.GetGeneration()
+					key := fmt.Sprintf("%s[%s]", u.GetKind(), got.Type)
+					if !ogReport[key] {
+						ogReport[key] = true
+						t.Logf("https://github.com/crossplane/crossplane/issues/6420: Warning, an unset observedGeneration was artificially updated for %s.status.conditions[%s]", u.GetKind(), got.Type)
+					}
+				}
+
 				if !got.Equal(old[i]) {
 					old[i] = got
-					t.Logf("- CONDITION: %s: %s=%s Reason=%s: %s (%s)", identifier(u), got.Type, got.Status, got.Reason, or(got.Message, `""`), got.LastTransitionTime)
+					t.Logf("- CONDITION: %s[@%d]: %s=%s[@%d] Reason=%s: %s (%s)",
+						identifier(u), u.GetGeneration(), got.Type, got.Status, got.ObservedGeneration, got.Reason, or(got.Message, `""`), got.LastTransitionTime)
 				}
 
 				// do compare modulo message as the message in e2e tests