Repository: argoproj/argo-cd
Pull request #22944 (2025-05-12 20:39:15)

fix(server): infer resource status health for apps-in-any-ns
The cache key will include the app namespace if the app is outside the controller namespace.


Patch:
diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
index 7a90625c2..e1afc4a76 100644
--- a/.github/workflows/ci-build.yaml
+++ b/.github/workflows/ci-build.yaml
@@ -174,7 +174,7 @@ jobs:
       - name: Run all unit tests
         run: make test-local
       - name: Generate test results artifacts
-        uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
         with:
           name: test-results
           path: test-results
@@ -238,7 +238,7 @@ jobs:
       - name: Run all unit tests
         run: make test-race-local
       - name: Generate test results artifacts
-        uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
         with:
           name: race-results
           path: test-results/
@@ -385,9 +385,9 @@ jobs:
         run: |
           go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
       - name: Upload code coverage information to codecov.io
-        uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
+        uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
         with:
-          file: test-results/full-coverage.out
+          files: test-results/full-coverage.out
           fail_ci_if_error: true
         env:
           CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -494,7 +494,7 @@ jobs:
           git config --global user.email "john.doe@example.com"
       - name: Pull Docker image required for tests
         run: |
-          docker pull ghcr.io/dexidp/dex:v2.41.1
+          docker pull ghcr.io/dexidp/dex:v2.43.0
           docker pull argoproj/argo-cd-ci-builder:v1.0.0
           docker pull redis:7.2.7-alpine
       - name: Create target directory for binaries in the build-process
@@ -526,13 +526,13 @@ jobs:
           goreman run stop-all || echo "goreman trouble"
           sleep 30
       - name: Upload e2e coverage report
-        uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
         with:
           name: e2e-code-coverage
           path: /tmp/coverage
         if: ${{ matrix.k3s.latest }}
       - name: Upload e2e-server logs
-        uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
         with:
           name: e2e-server-k8s${{ matrix.k3s.version }}.log
           path: /tmp/e2e-server.log
diff --git a/.github/workflows/scorecard.yaml b/.github/workflows/scorecard.yaml
index 0403b1e2d..64650e561 100644
--- a/.github/workflows/scorecard.yaml
+++ b/.github/workflows/scorecard.yaml
@@ -54,7 +54,7 @@ jobs:
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
         with:
           name: SARIF file
           path: results.sarif
diff --git a/USERS.md b/USERS.md
index 31068c7e7..9ef7bced2 100644
--- a/USERS.md
+++ b/USERS.md
@@ -350,6 +350,7 @@ Currently, the following organizations are **officially** using Argo CD:
 1. [Syncier](https://syncier.com/)
 1. [Synergy](https://synergy.net.au)
 1. [Syself](https://syself.com)
+1. [T-ROC Global](https://trocglobal.com/)
 1. [TableCheck](https://tablecheck.com/)
 1. [Tailor Brands](https://www.tailorbrands.com)
 1. [Tamkeen Technologies](https://tamkeentech.sa/)
@@ -412,5 +413,3 @@ Currently, the following organizations are **officially** using Argo CD:
 1. [Zimpler](https://www.zimpler.com/)
 1. [ZipRecruiter](https://www.ziprecruiter.com/)
 1. [ZOZO](https://corp.zozo.com/)
-
-
diff --git a/cmd/argocd-application-controller/commands/argocd_application_controller.go b/cmd/argocd-application-controller/commands/argocd_application_controller.go
index 49a71f746..d9f3e2d9c 100644
--- a/cmd/argocd-application-controller/commands/argocd_application_controller.go
+++ b/cmd/argocd-application-controller/commands/argocd_application_controller.go
@@ -68,6 +68,7 @@ func NewCommand() *cobra.Command {
 		selfHealBackoffTimeoutSeconds    int
 		selfHealBackoffFactor            int
 		selfHealBackoffCapSeconds        int
+		selfHealBackoffCooldownSeconds   int
 		syncTimeout                      int
 		statusProcessors                 int
 		operationProcessors              int
@@ -201,6 +202,7 @@ func NewCommand() *cobra.Command {
 				time.Duration(appResyncJitter)*time.Second,
 				time.Duration(selfHealTimeoutSeconds)*time.Second,
 				selfHealBackoff,
+				time.Duration(selfHealBackoffCooldownSeconds)*time.Second,
 				time.Duration(syncTimeout)*time.Second,
 				time.Duration(repoErrorGracePeriod)*time.Second,
 				metricsPort,
@@ -272,6 +274,7 @@ func NewCommand() *cobra.Command {
 	command.Flags().IntVar(&selfHealBackoffTimeoutSeconds, "self-heal-backoff-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS", 2, 0, math.MaxInt32), "Specifies initial timeout of exponential backoff between self heal attempts")
 	command.Flags().IntVar(&selfHealBackoffFactor, "self-heal-backoff-factor", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR", 3, 0, math.MaxInt32), "Specifies factor of exponential timeout between application self heal attempts")
 	command.Flags().IntVar(&selfHealBackoffCapSeconds, "self-heal-backoff-cap-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS", 300, 0, math.MaxInt32), "Specifies max timeout of exponential backoff between application self heal attempts")
+	command.Flags().IntVar(&selfHealBackoffCooldownSeconds, "self-heal-backoff-cooldown-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS", 330, 0, math.MaxInt32), "Specifies period of time the app needs to stay synced before the self heal backoff can reset")
 	command.Flags().IntVar(&syncTimeout, "sync-timeout", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT", 0, 0, math.MaxInt32), "Specifies the timeout after which a sync would be terminated. 0 means no timeout (default 0).")
 	command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", env.ParseInt64FromEnv("ARGOCD_APPLICATION_CONTROLLER_KUBECTL_PARALLELISM_LIMIT", 20, 0, math.MaxInt64), "Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit.")
 	command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server")
diff --git a/controller/appcontroller.go b/controller/appcontroller.go
index 8a020f20c..a82ecac76 100644
--- a/controller/appcontroller.go
+++ b/controller/appcontroller.go
@@ -41,6 +41,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
+	"k8s.io/utils/ptr"
 
 	commitclient "github.com/argoproj/argo-cd/v3/commitserver/apiclient"
 	"github.com/argoproj/argo-cd/v3/common"
@@ -125,6 +126,7 @@ type ApplicationController struct {
 	statusRefreshJitter           time.Duration
 	selfHealTimeout               time.Duration
 	selfHealBackOff               *wait.Backoff
+	selfHealBackoffCooldown       time.Duration
 	syncTimeout                   time.Duration
 	db                            db.ArgoDB
 	settingsMgr                   *settings_util.SettingsManager
@@ -160,6 +162,7 @@ func NewApplicationController(
 	appResyncJitter time.Duration,
 	selfHealTimeout time.Duration,
 	selfHealBackoff *wait.Backoff,
+	selfHealBackoffCooldown time.Duration,
 	syncTimeout time.Duration,
 	repoErrorGracePeriod time.Duration,
 	metricsPort int,
@@ -206,6 +209,7 @@ func NewApplicationController(
 		settingsMgr:                       settingsMgr,
 		selfHealTimeout:                   selfHealTimeout,
 		selfHealBackOff:                   selfHealBackoff,
+		selfHealBackoffCooldown:           selfHealBackoffCooldown,
 		syncTimeout:                       syncTimeout,
 		clusterSharding:                   clusterSharding,
 		projByNameCache:                   sync.Map{},
@@ -2240,17 +2244,22 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application, alread
 		return true, time.Duration(0)
 	}
 
-	// Reset counter if the prior sync was successful OR if the revision has changed
-	if !alreadyAttempted || app.Status.Sync.Status == appv1.SyncStatusCodeSynced {
+	var timeSinceOperation *time.Duration
+	if app.Status.OperationState.FinishedAt != nil {
+		timeSinceOperation = ptr.To(time.Since(app.Status.OperationState.FinishedAt.Time))
+	}
+
+	// Reset counter if the prior sync was successful and the cooldown period is over OR if the revision has changed
+	if !alreadyAttempted || (timeSinceOperation != nil && *timeSinceOperation >= ctrl.selfHealBackoffCooldown && app.Status.Sync.Status == appv1.SyncStatusCodeSynced) {
 		app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount = 0
 	}
 
 	var retryAfter time.Duration
 	if ctrl.selfHealBackOff == nil {
-		if app.Status.OperationState.FinishedAt == nil {
+		if timeSinceOperation == nil {
 			retryAfter = ctrl.selfHealTimeout
 		} else {
-			retryAfter = ctrl.selfHealTimeout - time.Since(app.Status.OperationState.FinishedAt.Time)
+			retryAfter = ctrl.selfHealTimeout - *timeSinceOperation
 		}
 	} else {
 		backOff := *ctrl.selfHealBackOff
@@ -2260,10 +2269,11 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application, alread
 		for i := 0; i < steps; i++ {
 			delay = backOff.Step()
 		}
-		if app.Status.OperationState.FinishedAt == nil {
+
+		if timeSinceOperation == nil {
 			retryAfter = delay
 		} else {
-			retryAfter = delay - time.Since(app.Status.OperationState.FinishedAt.Time)
+			retryAfter = delay - *timeSinceOperation
 		}
 	}
 	return retryAfter <= 0, retryAfter
diff --git a/docs/operator-manual/health.md b/docs/operator-manual/health.md
index 468f13407..cdaf053ea 100644
--- a/docs/operator-manual/health.md
+++ b/docs/operator-manual/health.md
@@ -168,7 +168,33 @@ To test the implemented custom health checks, run `go test -v ./util/lua/`.
 
 The [PR#1139](https://github.com/argoproj/argo-cd/pull/1139) is an example of Cert Manager CRDs custom health check.
 
-Please note that bundled health checks with wildcards are not supported.
+#### Wildcard Support for Built-in Health Checks
+
+You can use a single health check for multiple resources by using a wildcard in the group or kind directory names.
+
+The `_` character behaves like a `*` wildcard. For example, consider the following directory structure:
+
+```
+argo-cd
+|-- resource_customizations
+|    |-- _.group.io               # CRD group
+|    |    |-- _                   # Resource kind
+|    |    |    |-- health.lua     # Health check
+```
+
+Any resource with a group that ends with `.group.io` will use the health check in `health.lua`.
+
+Wildcard checks are only evaluated if there is no specific check for the resource.
+
+If multiple wildcard checks match, the first one in the directory structure is used.
+
+We use the [doublestar](https://github.com/bmatcuk/doublestar) glob library to match the wildcard checks. We currently
+only treat a path as a wildcard if it contains a `_` character, but this may change in the future.
+
+!!!important "Avoid Massive Scripts"
+
+    Avoid writing massive scripts to handle multiple resources. They'll get hard to read and maintain. Instead, just
+    duplicate the relevant parts in resource-specific scripts.
 
 ## Overriding Go-Based Health Checks
 
diff --git a/docs/operator-manual/server-commands/argocd-application-controller.md b/docs/operator-manual/server-commands/argocd-application-controller.md
index 852c62456..29c6b95d7 100644
--- a/docs/operator-manual/server-commands/argocd-application-controller.md
+++ b/docs/operator-manual/server-commands/argocd-application-controller.md
@@ -71,6 +71,7 @@ argocd-application-controller [flags]
       --repo-server-timeout-seconds int                           Repo server RPC call timeout seconds. (default 60)
       --request-timeout string                                    The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
       --self-heal-backoff-cap-seconds int                         Specifies max timeout of exponential backoff between application self heal attempts (default 300)
+      --self-heal-backoff-cooldown-seconds int                    Specifies period of time the app needs to stay synced before the self heal backoff can reset (default 330)
       --self-heal-backoff-factor int                              Specifies factor of exponential timeout between application self heal attempts (default 3)
       --self-heal-backoff-timeout-seconds int                     Specifies initial timeout of exponential backoff between self heal attempts (default 2)
       --self-heal-timeout-seconds int                             Specifies timeout between application self heal attempts
diff --git a/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml b/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml
index ea40f9c9a..bf65aed35 100644
--- a/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml
+++ b/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml
@@ -121,6 +121,12 @@ spec:
               name: argocd-cmd-params-cm
               key: controller.self.heal.backoff.cap.seconds
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              name: argocd-cmd-params-cm
+              key: controller.self.heal.backoff.cooldown.seconds
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/base/application-controller/argocd-application-controller-statefulset.yaml b/manifests/base/application-controller/argocd-application-controller-statefulset.yaml
index a595b75f8..e33d3917f 100644
--- a/manifests/base/application-controller/argocd-application-controller-statefulset.yaml
+++ b/manifests/base/application-controller/argocd-application-controller-statefulset.yaml
@@ -124,6 +124,12 @@ spec:
               name: argocd-cmd-params-cm
               key: controller.self.heal.backoff.cap.seconds
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              name: argocd-cmd-params-cm
+              key: controller.self.heal.backoff.cooldown.seconds
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/base/dex/argocd-dex-server-deployment.yaml b/manifests/base/dex/argocd-dex-server-deployment.yaml
index 0ef89cd75..1c1203b63 100644
--- a/manifests/base/dex/argocd-dex-server-deployment.yaml
+++ b/manifests/base/dex/argocd-dex-server-deployment.yaml
@@ -37,7 +37,7 @@ spec:
             type: RuntimeDefault
       containers:
       - name: dex
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         command: [/shared/argocd-dex, rundex]
         env:
diff --git a/manifests/core-install-with-hydrator.yaml b/manifests/core-install-with-hydrator.yaml
index cc4cdef63..1feb53fcf 100644
--- a/manifests/core-install-with-hydrator.yaml
+++ b/manifests/core-install-with-hydrator.yaml
@@ -25456,6 +25456,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/core-install.yaml b/manifests/core-install.yaml
index 9dea88c51..b38872875 100644
--- a/manifests/core-install.yaml
+++ b/manifests/core-install.yaml
@@ -25290,6 +25290,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/ha/install-with-hydrator.yaml b/manifests/ha/install-with-hydrator.yaml
index 468841fa5..d8020d1d6 100644
--- a/manifests/ha/install-with-hydrator.yaml
+++ b/manifests/ha/install-with-hydrator.yaml
@@ -26301,7 +26301,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -27501,6 +27501,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml
index f7880bb48..801d81972 100644
--- a/manifests/ha/install.yaml
+++ b/manifests/ha/install.yaml
@@ -26137,7 +26137,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -27337,6 +27337,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/ha/namespace-install-with-hydrator.yaml b/manifests/ha/namespace-install-with-hydrator.yaml
index 5ce09327c..e3f092880 100644
--- a/manifests/ha/namespace-install-with-hydrator.yaml
+++ b/manifests/ha/namespace-install-with-hydrator.yaml
@@ -2110,7 +2110,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -3310,6 +3310,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml
index 6cdf81e20..c22cffa53 100644
--- a/manifests/ha/namespace-install.yaml
+++ b/manifests/ha/namespace-install.yaml
@@ -1946,7 +1946,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -3146,6 +3146,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/install-with-hydrator.yaml b/manifests/install-with-hydrator.yaml
index 90ffd7004..3970649c6 100644
--- a/manifests/install-with-hydrator.yaml
+++ b/manifests/install-with-hydrator.yaml
@@ -25395,7 +25395,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -26545,6 +26545,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/install.yaml b/manifests/install.yaml
index a2e82b3a5..8c89a3ab3 100644
--- a/manifests/install.yaml
+++ b/manifests/install.yaml
@@ -25229,7 +25229,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -26379,6 +26379,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/namespace-install-with-hydrator.yaml b/manifests/namespace-install-with-hydrator.yaml
index 0dd6a1147..a86a9f8cd 100644
--- a/manifests/namespace-install-with-hydrator.yaml
+++ b/manifests/namespace-install-with-hydrator.yaml
@@ -1204,7 +1204,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -2354,6 +2354,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml
index 85aee13ab..752b64b10 100644
--- a/manifests/namespace-install.yaml
+++ b/manifests/namespace-install.yaml
@@ -1038,7 +1038,7 @@ spec:
               key: dexserver.disable.tls
               name: argocd-cmd-params-cm
               optional: true
-        image: ghcr.io/dexidp/dex:v2.41.1
+        image: ghcr.io/dexidp/dex:v2.43.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -2188,6 +2188,12 @@ spec:
               key: controller.self.heal.backoff.cap.seconds
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS
+          valueFrom:
+            configMapKeyRef:
+              key: controller.self.heal.backoff.cooldown.seconds
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT
           valueFrom:
             configMapKeyRef:
diff --git a/resource_customizations/_.crossplane.io/_/health.lua b/resource_customizations/_.crossplane.io/_/health.lua
new file mode 100644
index 000000000..c4cd585cf
--- /dev/null
+++ b/resource_customizations/_.crossplane.io/_/health.lua
@@ -0,0 +1,66 @@
+-- Health check copied from here: https://github.com/crossplane/docs/blob/bd701357e9d5eecf529a0b42f23a78850a6d1d87/content/master/guides/crossplane-with-argo-cd.md
+
+health_status = {
+  status = "Progressing",
+  message = "Provisioning ..."
+}
+
+local function contains (table, val)
+  for i, v in ipairs(table) do
+    if v == val then
+      return true
+    end
+  end
+  return false
+end
+
+local has_no_status = {
+  "Composition",
+  "CompositionRevision",
+  "DeploymentRuntimeConfig",
+  "ControllerConfig",
+  "ProviderConfig",
+  "ProviderConfigUsage"
+}
+if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
+    health_status.status = "Healthy"
+    health_status.message = "Resource is up-to-date."
+  return health_status
+end
+
+if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
+  if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
+    health_status.status = "Healthy"
+    health_status.message = "Resource is in use."
+    return health_status
+  end
+  return health_status
+end
+
+for i, condition in ipairs(obj.status.conditions) do
+  if condition.type == "LastAsyncOperation" then
+    if condition.status == "False" then
+      health_status.status = "Degraded"
+      health_status.message = condition.message
+      return health_status
+    end
+  end
+
+  if condition.type == "Synced" then
+    if condition.status == "False" then
+      health_status.status = "Degraded"
+      health_status.message = condition.message
+      return health_status
+    end
+  end
+
+  if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
+    if condition.status == "True" then
+      health_status.status = "Healthy"
+      health_status.message = "Resource is up-to-date."
+      return health_status
+    end
+  end
+end
+
+return health_status
diff --git a/resource_customizations/_.upbound.io/_/health.lua b/resource_customizations/_.upbound.io/_/health.lua
new file mode 100644
index 000000000..57869c81f
--- /dev/null
+++ b/resource_customizations/_.upbound.io/_/health.lua
@@ -0,0 +1,63 @@
+-- Health check copied from here: https://github.com/crossplane/docs/blob/bd701357e9d5eecf529a0b42f23a78850a6d1d87/content/master/guides/crossplane-with-argo-cd.md
+
+health_status = {
+  status = "Progressing",
+  message = "Provisioning ..."
+}
+
+local function contains (table, val)
+  for i, v in ipairs(table) do
+    if v == val then
+      return true
+    end
+  end
+  return false
+end
+
+local has_no_status = {
+  "ProviderConfig",
+  "ProviderConfigUsage"
+}
+
+if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
+  health_status.status = "Healthy"
+  health_status.message = "Resource is up-to-date."
+  return health_status
+end
+
+if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
+  if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
+    health_status.status = "Healthy"
+    health_status.message = "Resource is in use."
+    return health_status
+  end
+  return health_status
+end
+
+for i, condition in ipairs(obj.status.conditions) do
+  if condition.type == "LastAsyncOperation" then
+    if condition.status == "False" then
+      health_status.status = "Degraded"
+      health_status.message = condition.message
+      return health_status
+    end
+  end
+
+  if condition.type == "Synced" then
+    if condition.status == "False" then
+      health_status.status = "Degraded"
+      health_status.message = condition.message
+      return health_status
+    end
+  end
+
+  if condition.type == "Ready" then
+    if condition.status == "True" then
+      health_status.status = "Healthy"
+      health_status.message = "Resource is up-to-date."
+      return health_status
+    end
+  end
+end
+
+return health_status
diff --git a/resource_customizations/embed.go b/resource_customizations/embed.go
index 8a4d5316c..8b2157b82 100644
--- a/resource_customizations/embed.go
+++ b/resource_customizations/embed.go
@@ -6,5 +6,5 @@ import (
 
 // Embedded contains embedded resource customization
 //
-//go:embed *
+//go:embed all:*
 var Embedded embed.FS
diff --git a/resource_customizations/external-secrets.io/ExternalSecret/actions/discovery.lua b/resource_customizations/external-secrets.io/ExternalSecret/actions/discovery.lua
index 89d806c8d..7afe48d9c 100644
--- a/resource_customizations/external-secrets.io/ExternalSecret/actions/discovery.lua
+++ b/resource_customizations/external-secrets.io/ExternalSecret/actions/discovery.lua
@@ -1,3 +1,15 @@
 local actions = {}
-actions["refresh"] = {["disabled"] = false}
+
+local disable_refresh = false
+local time_units = {"ns", "us", "µs", "ms", "s", "m", "h"}
+local digits = obj.spec.refreshInterval
+for _, time_unit in ipairs(time_units) do
+  digits, _ = digits:gsub(time_unit, "")
+  if tonumber(digits) == 0 then
+    disable_refresh = true
+    break
+  end
+end
+
+actions["refresh"] = {["disabled"] = disable_refresh}
 return actions
diff --git a/resource_customizations/external-secrets.io/PushSecret/actions/discovery.lua b/resource_customizations/external-secrets.io/PushSecret/actions/discovery.lua
index 6b095fbd9..ab0b8883a 100644
--- a/resource_customizations/external-secrets.io/PushSecret/actions/discovery.lua
+++ b/resource_customizations/external-secrets.io/PushSecret/actions/discovery.lua
@@ -1,3 +1,15 @@
-actions = {}
-actions["push"] = {["disabled"] = false}
+local actions = {}
+
+local disable_push = false
+local time_units = {"ns", "us", "µs", "ms", "s", "m", "h"}
+local digits = obj.spec.refreshInterval
+for _, time_unit in ipairs(time_units) do
+  digits, _ = digits:gsub(time_unit, "")
+  if tonumber(digits) == 0 then
+    disable_push = true
+    break
+  end
+end
+
+actions["push"] = {["disabled"] = disable_push}
 return actions
diff --git a/resource_customizations/keda.sh/ScaledObject/health.lua b/resource_customizations/keda.sh/ScaledObject/health.lua
index 84cc5ad17..2f67e99e2 100644
--- a/resource_customizations/keda.sh/ScaledObject/health.lua
+++ b/resource_customizations/keda.sh/ScaledObject/health.lua
@@ -17,6 +17,10 @@ if obj.status ~= nil then
         hs.message = condition.message
         suspended = true
       end
+      if condition.status == "True" and condition.type == "Fallback" then
+        hs.message = condition.message
+        degraded = true
+      end
     end
   end
 end
@@ -32,4 +36,4 @@ elseif healthy == true and suspended == true then
 end
 hs.status = "Progressing"
 hs.message = "Creating HorizontalPodAutoscaler Object"
-return hs
\ No newline at end of file
+return hs
diff --git a/resource_customizations/projectcontour.io/HTTPProxy/health.lua b/resource_customizations/projectcontour.io/HTTPProxy/health.lua
new file mode 100644
index 000000000..edc74d6da
--- /dev/null
+++ b/resource_customizations/projectcontour.io/HTTPProxy/health.lua
@@ -0,0 +1,19 @@
+-- Status reporting information detailed here
+-- https://projectcontour.io/docs/main/config/fundamentals/#status-reporting
+hs = {
+  status = "Progressing",
+  message = "Waiting for status",
+}
+
+if obj.status ~= nil then
+  if obj.status.currentStatus ~= nil then
+    if obj.status.currentStatus == "valid" then
+      hs.status = "Healthy"
+    elseif obj.status.currentStatus == "invalid" then
+      hs.status = "Degraded"
+    end
+    hs.message = obj.status.description
+  end
+end
+
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Binding/health.lua b/resource_customizations/rabbitmq.com/Binding/health.lua
new file mode 100644
index 000000000..b2b96e8c8
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Binding/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ binding ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ binding failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ binding status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Exchange/health.lua b/resource_customizations/rabbitmq.com/Exchange/health.lua
new file mode 100644
index 000000000..54b9b1dae
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Exchange/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ exchange ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ exchange failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ exchange status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Permission/health.lua b/resource_customizations/rabbitmq.com/Permission/health.lua
new file mode 100644
index 000000000..f4e8beda5
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Permission/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ permission ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ permission failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ permission status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Policy/health.lua b/resource_customizations/rabbitmq.com/Policy/health.lua
new file mode 100644
index 000000000..f44b54322
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Policy/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ policy ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ policy failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ policy status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Queue/health.lua b/resource_customizations/rabbitmq.com/Queue/health.lua
new file mode 100644
index 000000000..80e4c4ab9
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Queue/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ queue ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ queue failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ queue status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Shovel/health.lua b/resource_customizations/rabbitmq.com/Shovel/health.lua
new file mode 100644
index 000000000..6aa365909
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Shovel/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ shovel ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ shovel failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ shovel status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/User/health.lua b/resource_customizations/rabbitmq.com/User/health.lua
new file mode 100644
index 000000000..77f69b1a5
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/User/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ user ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ user failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ user status is unknown"
+return hs
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Vhost/health.lua b/resource_customizations/rabbitmq.com/Vhost/health.lua
new file mode 100644
index 000000000..70d05ff35
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Vhost/health.lua
@@ -0,0 +1,22 @@
+local hs = {}
+if obj.status ~= nil and obj.status.conditions ~= nil then
+  for i, condition in ipairs(obj.status.conditions) do
+    if condition.type == "Ready" then
+      if condition.status == "True" and condition.reason == "SuccessfulCreateOrUpdate" then
+        hs.status = "Healthy"
+        hs.message = "RabbitMQ vhost ready"
+        return hs
+      end 
+
+      if condition.status == "False" and condition.reason == "FailedCreateOrUpdate" then
+        hs.status = "Degraded"
+        hs.message = "RabbitMQ vhost failed to be created or updated"
+        return hs
+      end
+    end
+  end
+end
+
+hs.status = "Unknown"
+hs.message = "RabbitMQ vhost status is unknown"
+return hs
\ No newline at end of file
diff --git a/server/application/application.go b/server/application/application.go
index b9f1d38ce..0a4e52eb5 100644
--- a/server/application/application.go
+++ b/server/application/application.go
@@ -2679,7 +2679,7 @@ func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.A
 func (s *Server) inferResourcesStatusHealth(app *v1alpha1.Application) {
 	if app.Status.ResourceHealthSource == v1alpha1.ResourceHealthLocationAppTree {
 		tree := &v1alpha1.ApplicationTree{}
-		if err := s.cache.GetAppResourcesTree(app.Name, tree); err == nil {
+		if err := s.cache.GetAppResourcesTree(app.InstanceName(s.ns), tree); err == nil {
 			healthByKey := map[kube.ResourceKey]*v1alpha1.HealthStatus{}
 			for _, node := range tree.Nodes {
 				if node.Health != nil {
diff --git a/ui/package.json b/ui/package.json
index e7face930..b0ec7e709 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -97,7 +97,7 @@
     "babel-loader": "^8.0.6",
     "codecov": "^3.8.3",
     "copy-webpack-plugin": "^6.1.1",
-    "esbuild-loader": "^2.18.0",
+    "esbuild-loader": "^4.3.0",
     "eslint": "^9.15.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-prettier": "^5.1.3",
diff --git a/ui/yarn.lock b/ui/yarn.lock
index e569c5ed6..57dfb4c70 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -1119,33 +1119,10 @@
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-transform-typescript" "^7.7.2"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.8.4":
-  version "7.14.6"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
-  integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
-  dependencies:
-    regenerator-runtime "^0.13.4"
-
-"@babel/runtime@^7.17.8":
-  version "7.26.9"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433"
-  integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==
-  dependencies:
-    regenerator-runtime "^0.14.0"
-
-"@babel/runtime@^7.21.0":
-  version "7.21.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
-  integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
-  dependencies:
-    regenerator-runtime "^0.13.11"
-
-"@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7":
-  version "7.20.13"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
-  integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
-  dependencies:
-    regenerator-runtime "^0.13.11"
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.21.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541"
+  integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
 
 "@babel/template@^7.14.5", "@babel/template@^7.7.0":
   version "7.14.5"
@@ -1298,6 +1275,131 @@
     es-module-lexer "^0.10.5"
     get-tsconfig "^3.0.1"
 
+"@esbuild/aix-ppc64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64"
+  integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==
+
+"@esbuild/android-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f"
+  integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==
+
+"@esbuild/android-arm@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b"
+  integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==
+
+"@esbuild/android-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163"
+  integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==
+
+"@esbuild/darwin-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz#fa394164b0d89d4fdc3a8a21989af70ef579fa2c"
+  integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==
+
+"@esbuild/darwin-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a"
+  integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==
+
+"@esbuild/freebsd-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce"
+  integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==
+
+"@esbuild/freebsd-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7"
+  integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==
+
+"@esbuild/linux-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73"
+  integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==
+
+"@esbuild/linux-arm@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3"
+  integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==
+
+"@esbuild/linux-ia32@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19"
+  integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==
+
+"@esbuild/linux-loong64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7"
+  integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==
+
+"@esbuild/linux-mips64el@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1"
+  integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==
+
+"@esbuild/linux-ppc64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951"
+  integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==
+
+"@esbuild/linux-riscv64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987"
+  integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==
+
+"@esbuild/linux-s390x@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4"
+  integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==
+
+"@esbuild/linux-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a"
+  integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==
+
+"@esbuild/netbsd-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b"
+  integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==
+
+"@esbuild/netbsd-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b"
+  integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==
+
+"@esbuild/openbsd-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7"
+  integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==
+
+"@esbuild/openbsd-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde"
+  integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==
+
+"@esbuild/sunos-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92"
+  integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==
+
+"@esbuild/win32-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c"
+  integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==
+
+"@esbuild/win32-ia32@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079"
+  integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==
+
+"@esbuild/win32-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b"
+  integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==
+
 "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -4241,213 +4343,111 @@ es6-promise@^3.2.1:
   resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
   integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=
 
-esbuild-android-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz#b868bbd9955a92309c69df628d8dd1945478b45c"
-  integrity sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==
-
 esbuild-android-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64"
   integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==
 
-esbuild-android-arm64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz#e7d6430555e8e9c505fd87266bbc709f25f1825c"
-  integrity sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==
-
 esbuild-android-arm64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8"
   integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==
 
-esbuild-darwin-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz#4dc7484127564e89b4445c0a560a3cb50b3d68e1"
-  integrity sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==
-
 esbuild-darwin-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz#e02b1291f629ebdc2aa46fabfacc9aa28ff6aa46"
   integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==
 
-esbuild-darwin-arm64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz#469e59c665f84a8ed323166624c5e7b9b2d22ac1"
-  integrity sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==
-
 esbuild-darwin-arm64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9"
   integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==
 
-esbuild-freebsd-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz#895df03bf5f87094a56c9a5815bf92e591903d70"
-  integrity sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==
-
 esbuild-freebsd-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e"
   integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==
 
-esbuild-freebsd-arm64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz#0b72a41a6b8655e9a8c5608f2ec1afdcf6958441"
-  integrity sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==
-
 esbuild-freebsd-arm64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6"
   integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==
 
-esbuild-linux-32@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz#43b8ba3803b0bbe7f051869c6a8bf6de1e95de28"
-  integrity sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==
-
 esbuild-linux-32@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70"
   integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==
 
-esbuild-linux-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz#dc8072097327ecfadba1735562824ce8c05dd0bd"
-  integrity sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==
-
 esbuild-linux-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519"
   integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==
 
-esbuild-linux-arm64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz#c52b58cbe948426b1559910f521b0a3f396f10b8"
-  integrity sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==
-
 esbuild-linux-arm64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a"
   integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==
 
-esbuild-linux-arm@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz#df869dbd67d4ee3a04b3c7273b6bd2b233e78a18"
-  integrity sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==
-
 esbuild-linux-arm@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986"
   integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==
 
-esbuild-linux-mips64le@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz#a2b646d9df368b01aa970a7b8968be6dd6b01d19"
-  integrity sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==
-
 esbuild-linux-mips64le@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5"
   integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==
 
-esbuild-linux-ppc64le@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz#9a21af766a0292578a3009c7408b8509cac7cefd"
-  integrity sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==
-
 esbuild-linux-ppc64le@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47"
   integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==
 
-esbuild-linux-riscv64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz#344a27f91568056a5903ad5841b447e00e78d740"
-  integrity sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==
-
 esbuild-linux-riscv64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2"
   integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==
 
-esbuild-linux-s390x@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz#73a7309bd648a07ef58f069658f989a5096130db"
-  integrity sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==
-
 esbuild-linux-s390x@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0"
   integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==
 
-esbuild-loader@^2.18.0:
-  version "2.18.0"
-  resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.18.0.tgz#7b9548578ab954574fd94655693d22aa5ec74120"
-  integrity sha512-AKqxM3bI+gvGPV8o6NAhR+cBxVO8+dh+O0OXBHIXXwuSGumckbPWHzZ17subjBGI2YEGyJ1STH7Haj8aCrwL/w==
+esbuild-loader@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-4.3.0.tgz#70a19717290cf59c6ac55b26782dbcf84378ccb4"
+  integrity sha512-D7HeJNdkDKKMarPQO/3dlJT6RwN2YJO7ENU6RPlpOz5YxSHnUNi2yvW41Bckvi1EVwctIaLzlb0ni5ag2GINYA==
   dependencies:
-    esbuild "^0.14.6"
-    joycon "^3.0.1"
-    json5 "^2.2.0"
-    loader-utils "^2.0.0"
-    tapable "^2.2.0"
-    webpack-sources "^2.2.0"
-
-esbuild-netbsd-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz#482a587cdbd18a6c264a05136596927deb46c30a"
-  integrity sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==
+    esbuild "^0.25.0"
+    get-tsconfig "^4.7.0"
+    loader-utils "^2.0.4"
+    webpack-sources "^1.4.3"
 
 esbuild-netbsd-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95"
   integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==
 
-esbuild-openbsd-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz#e99f8cdc63f1628747b63edd124d53cf7796468d"
-  integrity sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==
-
 esbuild-openbsd-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd"
   integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==
 
-esbuild-sunos-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz#8611d825bcb8239c78d57452e83253a71942f45c"
-  integrity sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==
-
 esbuild-sunos-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b"
   integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==
 
-esbuild-windows-32@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz#c06374206d4d92dd31d4fda299b09f51a35e82f6"
-  integrity sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==
-
 esbuild-windows-32@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1"
   integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==
 
-esbuild-windows-64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz#756631c1d301dfc0d1a887deed2459ce4079582f"
-  integrity sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==
-
 esbuild-windows-64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107"
   integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==
 
-esbuild-windows-arm64@0.14.27:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz#ad7e187193dcd18768b16065a950f4441d7173f4"
-  integrity sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==
-
 esbuild-windows-arm64@0.14.38:
   version "0.14.38"
   resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54"
@@ -4479,31 +4479,36 @@ esbuild@0.14.38:
     esbuild-windows-64 "0.14.38"
     esbuild-windows-arm64 "0.14.38"
 
-esbuild@^0.14.6:
-  version "0.14.27"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.27.tgz#41fe0f1b6b68b9f77cac025009bc54bb96e616f1"
-  integrity sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==
+esbuild@^0.25.0:
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92"
+  integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==
   optionalDependencies:
-    esbuild-android-64 "0.14.27"
-    esbuild-android-arm64 "0.14.27"
-    esbuild-darwin-64 "0.14.27"
-    esbuild-darwin-arm64 "0.14.27"
-    esbuild-freebsd-64 "0.14.27"
-    esbuild-freebsd-arm64 "0.14.27"
-    esbuild-linux-32 "0.14.27"
-    esbuild-linux-64 "0.14.27"
-    esbuild-linux-arm "0.14.27"
-    esbuild-linux-arm64 "0.14.27"
-    esbuild-linux-mips64le "0.14.27"
-    esbuild-linux-ppc64le "0.14.27"
-    esbuild-linux-riscv64 "0.14.27"
-    esbuild-linux-s390x "0.14.27"
-    esbuild-netbsd-64 "0.14.27"
-    esbuild-openbsd-64 "0.14.27"
-    esbuild-sunos-64 "0.14.27"
-    esbuild-windows-32 "0.14.27"
-    esbuild-windows-64 "0.14.27"
-    esbuild-windows-arm64 "0.14.27"
+    "@esbuild/aix-ppc64" "0.25.0"
+    "@esbuild/android-arm" "0.25.0"
+    "@esbuild/android-arm64" "0.25.0"
+    "@esbuild/android-x64" "0.25.0"
+    "@esbuild/darwin-arm64" "0.25.0"
+    "@esbuild/darwin-x64" "0.25.0"
+    "@esbuild/freebsd-arm64" "0.25.0"
+    "@esbuild/freebsd-x64" "0.25.0"
+    "@esbuild/linux-arm" "0.25.0"
+    "@esbuild/linux-arm64" "0.25.0"
+    "@esbuild/linux-ia32" "0.25.0"
+    "@esbuild/linux-loong64" "0.25.0"
+    "@esbuild/linux-mips64el" "0.25.0"
+    "@esbuild/linux-ppc64" "0.25.0"
+    "@esbuild/linux-riscv64" "0.25.0"
+    "@esbuild/linux-s390x" "0.25.0"
+    "@esbuild/linux-x64" "0.25.0"
+    "@esbuild/netbsd-arm64" "0.25.0"
+    "@esbuild/netbsd-x64" "0.25.0"
+    "@esbuild/openbsd-arm64" "0.25.0"
+    "@esbuild/openbsd-x64" "0.25.0"
+    "@esbuild/sunos-x64" "0.25.0"
+    "@esbuild/win32-arm64" "0.25.0"
+    "@esbuild/win32-ia32" "0.25.0"
+    "@esbuild/win32-x64" "0.25.0"
 
 escalade@^3.1.1:
   version "3.1.1"
@@ -5161,6 +5166,13 @@ get-tsconfig@^3.0.1:
   resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-3.0.1.tgz#02cadb5abc5f0d53033c8b2f3005b84134ba22e9"
   integrity sha512-+m30eQjbcf3xMNdnacXH5IDAKUMbI7Mhbf3e1BHif1FzBlUhBzBlmOVc7kL4+kB035l8OCyBdI3dNXZ3of9HqA==
 
+get-tsconfig@^4.7.0:
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz#403a682b373a823612475a4c2928c7326fc0f6bb"
+  integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==
+  dependencies:
+    resolve-pkg-maps "^1.0.0"
+
 git-up@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467"
@@ -6567,11 +6579,6 @@ jest@^29.7.0:
     import-local "^3.0.2"
     jest-cli "^29.7.0"
 
-joycon@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.0.1.tgz#9074c9b08ccf37a6726ff74a18485f85efcaddaf"
-  integrity sha512-SJcJNBg32dGgxhPtM0wQqxqV0ax9k/9TaUskGDSJkSFSQOEWWvQ3zzWdGQRIUry2j1zA5+ReH13t0Mf3StuVZA==
-
 js-levenshtein@^1.1.3, js-levenshtein@^1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
@@ -6685,7 +6692,7 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.0, json5@^2.1.2, json5@^2.2.0:
+json5@^2.1.0, json5@^2.1.2:
   version "2.2.2"
   resolved "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab"
   integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==
@@ -6754,7 +6761,7 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
     emojis-list "^3.0.0"
     json5 "^1.0.1"
 
-loader-utils@^2.0.0, loader-utils@^2.0.2:
+loader-utils@^2.0.0, loader-utils@^2.0.2, loader-utils@^2.0.4:
   version "2.0.4"
   resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
   integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
@@ -7701,9 +7708,9 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
     react-is "^18.0.0"
 
 prismjs@^1.29.0:
-  version "1.29.0"
-  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
-  integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+  version "1.30.0"
+  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9"
+  integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
 
 process-nextick-args@~2.0.0:
   version "2.0.1"
@@ -8178,21 +8185,6 @@ regenerator-runtime@^0.11.0:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
-regenerator-runtime@^0.13.11:
-  version "0.13.11"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
-  integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
-
-regenerator-runtime@^0.13.4:
-  version "0.13.7"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
-  integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
-
-regenerator-runtime@^0.14.0:
-  version "0.14.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
-  integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
-
 regenerator-transform@^0.14.2:
   version "0.14.5"
   resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
@@ -8305,6 +8297,11 @@ resolve-pathname@^3.0.0:
   resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
   integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
 
+resolve-pkg-maps@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
+  integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
+
 resolve.exports@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
@@ -8811,7 +8808,7 @@ sockjs@^0.3.21:
     uuid "^8.3.2"
     websocket-driver "^0.7.4"
 
-source-list-map@^2.0.0, source-list-map@^2.0.1:
+source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
   integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
@@ -9810,14 +9807,6 @@ webpack-sources@^1.4.3:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack-sources@^2.2.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd"
-  integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==
-  dependencies:
-    source-list-map "^2.0.1"
-    source-map "^0.6.1"
-
 webpack-sources@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
diff --git a/util/grpc/useragent.go b/util/grpc/useragent.go
index 8d49733b0..6d5d59c6b 100644
--- a/util/grpc/useragent.go
+++ b/util/grpc/useragent.go
@@ -12,15 +12,21 @@ import (
 	"google.golang.org/grpc/status"
 )
 
-// UserAgentUnaryServerInterceptor returns a UnaryServerInterceptor which enforces a minimum client
-// version in the user agent
-func UserAgentUnaryServerInterceptor(clientName, constraintStr string) grpc.UnaryServerInterceptor {
+// parseSemVerConstraint returns a semVer Constraint instance or panic if there is a parsing error with the provided constraint.
+func parseSemVerConstraint(constraintStr string) *semver.Constraints {
 	semVerConstraint, err := semver.NewConstraint(constraintStr)
 	if err != nil {
 		panic(err)
 	}
+	return semVerConstraint
+}
+
+// UserAgentUnaryServerInterceptor returns a UnaryServerInterceptor which enforces a minimum client
+// version in the user agent
+func UserAgentUnaryServerInterceptor(clientName, constraintStr string) grpc.UnaryServerInterceptor {
+	semVerConstraint := parseSemVerConstraint(constraintStr)
 	return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
-		if err := userAgentEnforcer(ctx, clientName, constraintStr, semVerConstraint); err != nil {
+		if err := userAgentEnforcer(ctx, clientName, semVerConstraint); err != nil {
 			return nil, err
 		}
 		return handler(ctx, req)
@@ -30,19 +36,16 @@ func UserAgentUnaryServerInterceptor(clientName, constraintStr string) grpc.Unar
 // UserAgentStreamServerInterceptor returns a StreamServerInterceptor which enforces a minimum client
 // version in the user agent
 func UserAgentStreamServerInterceptor(clientName, constraintStr string) grpc.StreamServerInterceptor {
-	semVerConstraint, err := semver.NewConstraint(constraintStr)
-	if err != nil {
-		panic(err)
-	}
+	semVerConstraint := parseSemVerConstraint(constraintStr)
 	return func(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
-		if err := userAgentEnforcer(stream.Context(), clientName, constraintStr, semVerConstraint); err != nil {
+		if err := userAgentEnforcer(stream.Context(), clientName, semVerConstraint); err != nil {
 			return err
 		}
 		return handler(srv, stream)
 	}
 }
 
-func userAgentEnforcer(ctx context.Context, clientName, constraintStr string, semVerConstraint *semver.Constraints) error {
+func userAgentEnforcer(ctx context.Context, clientName string, semVerConstraint *semver.Constraints) error {
 	var userAgents []string
 	if md, ok := metadata.FromIncomingContext(ctx); ok {
 		for _, ua := range md["user-agent"] {
@@ -52,7 +55,7 @@ func userAgentEnforcer(ctx context.Context, clientName, constraintStr string, se
 		}
 	}
 	if isLegacyClient(userAgents) {
-		return status.Errorf(codes.FailedPrecondition, "unsatisfied client version constraint: %s", constraintStr)
+		return status.Errorf(codes.FailedPrecondition, "unsatisfied client version constraint: %s", semVerConstraint)
 	}
 
 	for _, userAgent := range userAgents {
diff --git a/util/lua/lua.go b/util/lua/lua.go
index df47709b7..abdc88f84 100644
--- a/util/lua/lua.go
+++ b/util/lua/lua.go
@@ -6,12 +6,17 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"reflect"
+	"slices"
+	"strings"
+	"sync"
 	"time"
 
 	"github.com/argoproj/gitops-engine/pkg/health"
+	glob "github.com/bmatcuk/doublestar/v4"
 	lua "github.com/yuin/gopher-lua"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -20,7 +25,7 @@ import (
 	applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
 	appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
 	"github.com/argoproj/argo-cd/v3/resource_customizations"
-	"github.com/argoproj/argo-cd/v3/util/glob"
+	argoglob "github.com/argoproj/argo-cd/v3/util/glob"
 )
 
 const (
@@ -31,15 +36,8 @@ const (
 	actionDiscoveryScriptFile = "discovery.lua"
 )
 
-// ScriptDoesNotExistError is an error type for when a built-in script does not exist.
-type ScriptDoesNotExistError struct {
-	// ScriptName is the name of the script that does not exist.
-	ScriptName string
-}
-
-func (e ScriptDoesNotExistError) Error() string {
-	return fmt.Sprintf("built-in script %q does not exist", e.ScriptName)
-}
+// errScriptDoesNotExist is an error type for when a built-in script does not exist.
+var errScriptDoesNotExist = errors.New("built-in script does not exist")
 
 type ResourceHealthOverrides map[string]appv1.ResourceOverride
 
@@ -187,8 +185,16 @@ func (vm VM) GetHealthScript(obj *unstructured.Unstructured) (script string, use
 	// (as built-in scripts are files in folders, named after the GVK, currently there is no wildcard support for them)
 	builtInScript, err := vm.getPredefinedLuaScripts(key, healthScriptFile)
 	if err != nil {
-		var doesNotExist *ScriptDoesNotExistError
-		if errors.As(err, &doesNotExist) {
+		if errors.Is(err, errScriptDoesNotExist) {
+			// Try to find a wildcard built-in health script
+			builtInScript, err = getWildcardBuiltInHealthOverrideLua(key)
+			if err != nil {
+				return "", false, fmt.Errorf("error while fetching built-in health script: %w", err)
+			}
+			if builtInScript != "" {
+				return builtInScript, true, nil
+			}
+
 			// It's okay if no built-in health script exists. Just return an empty string and let the caller handle it.
 			return "", false, nil
 		}
@@ -422,8 +428,7 @@ func (vm VM) GetResourceActionDiscovery(obj *unstructured.Unstructured) ([]strin
 	discoveryKey := key + "/actions/"
 	discoveryScript, err := vm.getPredefinedLuaScripts(discoveryKey, actionDiscoveryScriptFile)
 	if err != nil {
-		var doesNotExistErr *ScriptDoesNotExistError
-		if errors.As(err, &doesNotExistErr) {
+		if errors.Is(err, errScriptDoesNotExist) {
 			// No worries, just return what we have.
 			return discoveryScripts, nil
 		}
@@ -477,7 +482,7 @@ func getWildcardHealthOverrideLua(overrides map[string]appv1.ResourceOverride, g
 	gvkKeyToMatch := GetConfigMapKey(gvk)
 
 	for key, override := range overrides {
-		if glob.Match(key, gvkKeyToMatch) && override.HealthLua != "" {
+		if argoglob.Match(key, gvkKeyToMatch) && override.HealthLua != "" {
 			return override.HealthLua, override.UseOpenLibs
 		}
 	}
@@ -488,13 +493,95 @@ func (vm VM) getPredefinedLuaScripts(objKey string, scriptFile string) (string,
 	data, err := resource_customizations.Embedded.ReadFile(filepath.Join(objKey, scriptFile))
 	if err != nil {
 		if os.IsNotExist(err) {
-			return "", &ScriptDoesNotExistError{ScriptName: objKey}
+			return "", errScriptDoesNotExist
 		}
 		return "", err
 	}
 	return string(data), nil
 }
 
+// globHealthScriptPathsOnce is a sync.Once instance to ensure that the globHealthScriptPaths are only initialized once.
+// The globs come from an embedded filesystem, so it won't change at runtime.
+var globHealthScriptPathsOnce sync.Once
+
+// globHealthScriptPaths is a cache for the glob patterns of directories containing health.lua files. Don't use this
+// directly, use getGlobHealthScriptPaths() instead.
+var globHealthScriptPaths []string
+
+// getGlobHealthScriptPaths returns the paths of the directories containing health.lua files where the path contains a
+// glob pattern. It uses a sync.Once to ensure that the paths are only initialized once.
+func getGlobHealthScriptPaths() ([]string, error) {
+	var err error
+	globHealthScriptPathsOnce.Do(func() {
+		// Walk through the embedded filesystem and get the directory names of all directories containing a health.lua.
+		var patterns []string
+		err = fs.WalkDir(resource_customizations.Embedded, ".", func(path string, d fs.DirEntry, err error) error {
+			if err != nil {
+				return fmt.Errorf("error walking path %q: %w", path, err)
+			}
+
+			// Skip non-directories at the top level
+			if d.IsDir() && filepath.Dir(path) == "." {
+				return nil
+			}
+
+			// Check if the directory contains a health.lua file
+			if filepath.Base(path) != healthScriptFile {
+				return nil
+			}
+
+			groupKindPath := filepath.Dir(path)
+			// Check if the path contains a wildcard. If it doesn't, skip it.
+			if !strings.Contains(groupKindPath, "_") {
+				return nil
+			}
+
+			pattern := strings.ReplaceAll(groupKindPath, "_", "*")
+			// Check that the pattern is valid.
+			if !glob.ValidatePattern(pattern) {
+				return fmt.Errorf("invalid glob pattern %q: %w", pattern, err)
+			}
+
+			patterns = append(patterns, groupKindPath)
+			return nil
+		})
+		if err != nil {
+			return
+		}
+
+		// Sort the patterns to ensure deterministic choice of wildcard directory for a given GK.
+		slices.Sort(patterns)
+
+		globHealthScriptPaths = patterns
+	})
+	if err != nil {
+		return nil, fmt.Errorf("error getting health script glob directories: %w", err)
+	}
+	return globHealthScriptPaths, nil
+}
+
+func getWildcardBuiltInHealthOverrideLua(objKey string) (string, error) {
+	// Check if the GVK matches any of the wildcard directories
+	globs, err := getGlobHealthScriptPaths()
+	if err != nil {
+		return "", fmt.Errorf("error getting health script globs: %w", err)
+	}
+	for _, g := range globs {
+		pattern := strings.ReplaceAll(g, "_", "*")
+		if !glob.PathMatchUnvalidated(pattern, objKey) {
+			continue
+		}
+
+		var script []byte
+		script, err = resource_customizations.Embedded.ReadFile(filepath.Join(g, healthScriptFile))
+		if err != nil {
+			return "", fmt.Errorf("error reading %q file in embedded filesystem: %w", filepath.Join(objKey, healthScriptFile), err)
+		}
+		return string(script), nil
+	}
+	return "", nil
+}
+
 func isValidHealthStatusCode(statusCode health.HealthStatusCode) bool {
 	switch statusCode {
 	case health.HealthStatusUnknown, health.HealthStatusProgressing, health.HealthStatusSuspended, health.HealthStatusHealthy, health.HealthStatusDegraded, health.HealthStatusMissing:


Test patch:
diff --git a/controller/appcontroller_test.go b/controller/appcontroller_test.go
index 12be8228f..8185b5462 100644
--- a/controller/appcontroller_test.go
+++ b/controller/appcontroller_test.go
@@ -172,6 +172,7 @@ func newFakeControllerWithResync(data *fakeData, appResyncPeriod time.Duration,
 		time.Second,
 		time.Minute,
 		nil,
+		time.Minute,
 		0,
 		time.Second*10,
 		common.DefaultPortArgoCDMetrics,
@@ -2642,10 +2643,18 @@ func TestSelfHealExponentialBackoff(t *testing.T) {
 		alreadyAttempted: false,
 		expectedAttempts: 0,
 		syncStatus:       v1alpha1.SyncStatusCodeOutOfSync,
-	}, {
+	}, { // backoff will not reset as finished tme isn't >= cooldown
 		attempts:         6,
-		finishedAt:       nil,
-		expectedDuration: 0,
+		finishedAt:       ptr.To(metav1.Now()),
+		expectedDuration: 120 * time.Second,
+		shouldSelfHeal:   false,
+		alreadyAttempted: true,
+		expectedAttempts: 6,
+		syncStatus:       v1alpha1.SyncStatusCodeSynced,
+	}, { // backoff will reset as finished time is >= cooldown
+		attempts:         40,
+		finishedAt:       &metav1.Time{Time: time.Now().Add(-(1 * time.Minute))},
+		expectedDuration: -60 * time.Second,
 		shouldSelfHeal:   true,
 		alreadyAttempted: true,
 		expectedAttempts: 0,
diff --git a/resource_customizations/_.crossplane.io/_/health_test.yaml b/resource_customizations/_.crossplane.io/_/health_test.yaml
new file mode 100644
index 000000000..7ef0da991
--- /dev/null
+++ b/resource_customizations/_.crossplane.io/_/health_test.yaml
@@ -0,0 +1,5 @@
+tests:
+  - healthStatus:
+      status: Healthy
+      message: "Resource is up-to-date."
+    inputPath: testdata/composition_healthy.yaml
diff --git a/resource_customizations/_.crossplane.io/_/testdata/composition_healthy.yaml b/resource_customizations/_.crossplane.io/_/testdata/composition_healthy.yaml
new file mode 100644
index 000000000..56ae5ac89
--- /dev/null
+++ b/resource_customizations/_.crossplane.io/_/testdata/composition_healthy.yaml
@@ -0,0 +1,25 @@
+# Taken from here May 9, 2025: https://docs.crossplane.io/latest/concepts/compositions/
+apiVersion: apiextensions.crossplane.io/v1
+kind: Composition
+metadata:
+  name: example
+spec:
+  compositeTypeRef:
+    apiVersion: custom-api.example.org/v1alpha1
+    kind: AcmeBucket
+  mode: Pipeline
+  pipeline:
+    - step: patch-and-transform
+      functionRef:
+        name: function-patch-and-transform
+      input:
+        apiVersion: pt.fn.crossplane.io/v1beta1
+        kind: Resources
+        resources:
+          - name: storage-bucket
+            base:
+              apiVersion: s3.aws.upbound.io/v1beta1
+              kind: Bucket
+              spec:
+                forProvider:
+                  region: "us-east-2"
diff --git a/resource_customizations/_.upbound.io/_/health_test.yaml b/resource_customizations/_.upbound.io/_/health_test.yaml
new file mode 100644
index 000000000..a11541d26
--- /dev/null
+++ b/resource_customizations/_.upbound.io/_/health_test.yaml
@@ -0,0 +1,5 @@
+tests:
+  - healthStatus:
+      status: Healthy
+      message: "Resource is up-to-date."
+    inputPath: testdata/providerconfig_healthy.yaml
diff --git a/resource_customizations/_.upbound.io/_/testdata/providerconfig_healthy.yaml b/resource_customizations/_.upbound.io/_/testdata/providerconfig_healthy.yaml
new file mode 100644
index 000000000..5429ac065
--- /dev/null
+++ b/resource_customizations/_.upbound.io/_/testdata/providerconfig_healthy.yaml
@@ -0,0 +1,10 @@
+apiVersion: aws.upbound.io/v1beta1
+kind: ProviderConfig
+metadata:
+  name: irsa-with-role-chaining
+spec:
+  credentials:
+    source: IRSA
+  assumeRoleChain:
+    - roleARN: <roleARN-1>
+    - roleARN: <roleARN-2>
diff --git a/resource_customizations/keda.sh/ScaledObject/health_test.yaml b/resource_customizations/keda.sh/ScaledObject/health_test.yaml
index 969334650..ef3ba2de9 100644
--- a/resource_customizations/keda.sh/ScaledObject/health_test.yaml
+++ b/resource_customizations/keda.sh/ScaledObject/health_test.yaml
@@ -19,3 +19,7 @@ tests:
     status: Suspended
     message: "ScaledObject is paused"
   inputPath: testdata/keda-suspended.yaml
+- healthStatus:
+    status: Degraded
+    message: "At least one trigger is falling back on this scaled object"
+  inputPath: testdata/keda-fallback.yaml
diff --git a/resource_customizations/keda.sh/ScaledObject/testdata/keda-fallback.yaml b/resource_customizations/keda.sh/ScaledObject/testdata/keda-fallback.yaml
new file mode 100644
index 000000000..6aaf7c1e0
--- /dev/null
+++ b/resource_customizations/keda.sh/ScaledObject/testdata/keda-fallback.yaml
@@ -0,0 +1,58 @@
+apiVersion: keda.sh/v1alpha1
+kind: ScaledObject
+metadata:
+  annotations:
+  finalizers:
+    - finalizer.keda.sh
+  labels:
+    argocd.argoproj.io/instance: keda-default
+  name: keda-with-fallback
+  namespace: keda
+  resourceVersion: '160591443'
+  uid: 83ee438a-f383-43f3-9346-b901d9773f5c
+spec:
+  maxReplicaCount: 10
+  minReplicaCount: 1
+  fallback:
+    failureThreshold: 3
+    replicas: 5
+  scaleTargetRef:
+    name: keda-service
+  triggers:
+    - type: prometheus
+      metadata:
+        serverAddress: http://prometheus-server.monitoring.svc.cluster.local
+        metricName: http_requests_total
+        threshold: '100'
+        query: sum(rate(http_requests_total{app="keda-service"}[2m]))
+status:
+  conditions:
+    - message: ScaledObject is defined correctly and is ready for scaling
+      reason: ScaledObjectReady
+      status: 'True'
+      type: Ready
+    - message: Scaling is performed because triggers are active
+      reason: ScalerActive
+      status: 'True'
+      type: Active
+    - message: At least one trigger is falling back on this scaled object
+      reason: FallbackExists
+      status: 'True'
+      type: Fallback
+    - status: 'False'
+      type: Paused
+  externalMetricNames:
+    - s0-prometheus
+  health:
+    "prometheus": 
+      numberOfFailures: 4
+      status: Failing
+  hpaName: keda-with-fallback-hpa
+  lastActiveTime: '2023-12-19T10:35:22Z'
+  originalReplicaCount: 1
+  scaleTargetGVKR:
+    group: apps
+    kind: Deployment
+    resource: deployments
+    version: v1
+  scaleTargetKind: apps/v1.Deployment
diff --git a/resource_customizations/projectcontour.io/HTTPProxy/health_test.yaml b/resource_customizations/projectcontour.io/HTTPProxy/health_test.yaml
new file mode 100644
index 000000000..2732b145e
--- /dev/null
+++ b/resource_customizations/projectcontour.io/HTTPProxy/health_test.yaml
@@ -0,0 +1,17 @@
+tests:
+- healthStatus:
+    status: Healthy
+    message: "Valid HTTPProxy"
+  inputPath: testdata/healthy.yaml
+- healthStatus:
+    status: Progressing
+    message: 'Waiting for status'
+  inputPath: testdata/progressing.yaml
+- healthStatus:
+    status: Progressing
+    message: 'Waiting for controller'
+  inputPath: testdata/not_reconciled.yaml
+- healthStatus:
+    status: Degraded
+    message: 'At least one error present, see Errors for details'
+  inputPath: testdata/degraded.yaml
diff --git a/resource_customizations/projectcontour.io/HTTPProxy/testdata/degraded.yaml b/resource_customizations/projectcontour.io/HTTPProxy/testdata/degraded.yaml
new file mode 100644
index 000000000..85d1c3eb5
--- /dev/null
+++ b/resource_customizations/projectcontour.io/HTTPProxy/testdata/degraded.yaml
@@ -0,0 +1,31 @@
+apiVersion: projectcontour.io/v1
+kind: HTTPProxy
+metadata:
+  name: basic
+spec:
+  virtualhost:
+    fqdn: foo-basic.bar.com
+  routes:
+    - conditions:
+        - prefix: /
+      services:
+        - name: s1
+          port: 80
+status:
+  conditions:
+  - errors:
+      - message: 'Spec.Routes unresolved service reference: service "default/s1" not found'
+        reason: ServiceUnresolvedReference
+        status: "True"
+        type: ServiceError
+    lastTransitionTime: "2025-04-07T10:00:00Z"
+    message: 'At least one error present, see Errors for details'
+    observedGeneration: 1
+    reason: ErrorPresent
+    status: "False"
+    type: Valid
+  currentStatus: invalid
+  description: 'At least one error present, see Errors for details'
+  loadBalancer:
+    ingress:
+    - hostname: abc-123.elb.us-east-1.amazonaws.com
diff --git a/resource_customizations/projectcontour.io/HTTPProxy/testdata/healthy.yaml b/resource_customizations/projectcontour.io/HTTPProxy/testdata/healthy.yaml
new file mode 100644
index 000000000..944cb1f51
--- /dev/null
+++ b/resource_customizations/projectcontour.io/HTTPProxy/testdata/healthy.yaml
@@ -0,0 +1,26 @@
+apiVersion: projectcontour.io/v1
+kind: HTTPProxy
+metadata:
+  name: basic
+spec:
+  virtualhost:
+    fqdn: foo-basic.bar.com
+  routes:
+    - conditions:
+        - prefix: /
+      services:
+        - name: s1
+          port: 80
+status:
+  conditions:
+  - type: Valid
+    status: "True"
+    observedGeneration: 1
+    lastTransitionTime: "2025-04-07T10:00:00Z"
+    reason: Valid
+    message: Valid HTTPProxy
+  currentStatus: valid
+  description: Valid HTTPProxy
+  loadBalancer:
+    ingress:
+      - hostname: www.example.com
diff --git a/resource_customizations/projectcontour.io/HTTPProxy/testdata/not_reconciled.yaml b/resource_customizations/projectcontour.io/HTTPProxy/testdata/not_reconciled.yaml
new file mode 100644
index 000000000..7cc018785
--- /dev/null
+++ b/resource_customizations/projectcontour.io/HTTPProxy/testdata/not_reconciled.yaml
@@ -0,0 +1,16 @@
+apiVersion: projectcontour.io/v1
+kind: HTTPProxy
+metadata:
+  name: basic
+spec:
+  virtualhost:
+    fqdn: foo-basic.bar.com
+  routes:
+    - conditions:
+        - prefix: /
+      services:
+        - name: s1
+          port: 80
+status:
+  currentStatus: NotReconciled
+  description: Waiting for controller
diff --git a/resource_customizations/projectcontour.io/HTTPProxy/testdata/progressing.yaml b/resource_customizations/projectcontour.io/HTTPProxy/testdata/progressing.yaml
new file mode 100644
index 000000000..86bece89d
--- /dev/null
+++ b/resource_customizations/projectcontour.io/HTTPProxy/testdata/progressing.yaml
@@ -0,0 +1,13 @@
+apiVersion: projectcontour.io/v1
+kind: HTTPProxy
+metadata:
+  name: basic
+spec:
+  virtualhost:
+    fqdn: foo-basic.bar.com
+  routes:
+    - conditions:
+        - prefix: /
+      services:
+        - name: s1
+          port: 80
diff --git a/resource_customizations/rabbitmq.com/Binding/health_test.yaml b/resource_customizations/rabbitmq.com/Binding/health_test.yaml
new file mode 100644
index 000000000..b1481c95f
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Binding/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ binding failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ binding status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ binding ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Binding/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Binding/testdata/degraded.yaml
new file mode 100644
index 000000000..d462a7a67
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Binding/testdata/degraded.yaml
@@ -0,0 +1,22 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Binding
+metadata:
+  labels:
+    app: example-rabbitmq
+  name: example-rabbitmq
+  namespace: example
+spec:
+  destination: destination
+  destinationType: queue
+  rabbitmqClusterReference:
+    name: example-rabbitmq
+  routingKey: v1.example.request
+  source: operation-requests
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Binding/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Binding/testdata/healthy.yaml
new file mode 100644
index 000000000..f3ec856dc
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Binding/testdata/healthy.yaml
@@ -0,0 +1,22 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Binding
+metadata:
+  labels:
+    app: example-rabbitmq
+  name: example-rabbitmq
+  namespace: example
+spec:
+  destination: destination
+  destinationType: queue
+  rabbitmqClusterReference:
+    name: example-rabbitmq
+  routingKey: v1.example.request
+  source: operation-requests
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Binding/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Binding/testdata/unknown.yaml
new file mode 100644
index 000000000..463d6a20c
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Binding/testdata/unknown.yaml
@@ -0,0 +1,16 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Binding
+metadata:
+  labels:
+    app: example-rabbitmq
+  name: example-rabbitmq
+  namespace: example
+spec:
+  destination: destination
+  destinationType: queue
+  rabbitmqClusterReference:
+    name: example-rabbitmq
+  routingKey: v1.example.request
+  source: operation-requests
+  vhost: /
+status: {}
diff --git a/resource_customizations/rabbitmq.com/Exchange/health_test.yaml b/resource_customizations/rabbitmq.com/Exchange/health_test.yaml
new file mode 100644
index 000000000..45f0ffd6c
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Exchange/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ exchange failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ exchange status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ exchange ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Exchange/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Exchange/testdata/degraded.yaml
new file mode 100644
index 000000000..c686453d1
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Exchange/testdata/degraded.yaml
@@ -0,0 +1,21 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Exchange
+metadata:
+  generation: 3
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  autoDelete: false
+  durable: true
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+  type: topic
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Exchange/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Exchange/testdata/healthy.yaml
new file mode 100644
index 000000000..bdcb7dcbb
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Exchange/testdata/healthy.yaml
@@ -0,0 +1,21 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Exchange
+metadata:
+  generation: 3
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  autoDelete: false
+  durable: true
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+  type: topic
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Exchange/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Exchange/testdata/unknown.yaml
new file mode 100644
index 000000000..d9a255f21
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Exchange/testdata/unknown.yaml
@@ -0,0 +1,15 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Exchange
+metadata:
+  generation: 3
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  autoDelete: false
+  durable: true
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+  type: topic
+  vhost: /
+status: {}
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Permission/health_test.yaml b/resource_customizations/rabbitmq.com/Permission/health_test.yaml
new file mode 100644
index 000000000..e0d9d24c5
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Permission/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ permission failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ permission status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ permission ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Permission/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Permission/testdata/degraded.yaml
new file mode 100644
index 000000000..b64446c3d
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Permission/testdata/degraded.yaml
@@ -0,0 +1,23 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Permission
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  permissions:
+    configure: .*
+    read: .*
+    write: .*
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: exmple-rabbit
+  user: example_user
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Permission/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Permission/testdata/healthy.yaml
new file mode 100644
index 000000000..495132db5
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Permission/testdata/healthy.yaml
@@ -0,0 +1,23 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Permission
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  permissions:
+    configure: .*
+    read: .*
+    write: .*
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: exmple-rabbit
+  user: example_user
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Permission/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Permission/testdata/unknown.yaml
new file mode 100644
index 000000000..a6294492b
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Permission/testdata/unknown.yaml
@@ -0,0 +1,17 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Permission
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  permissions:
+    configure: .*
+    read: .*
+    write: .*
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: exmple-rabbit
+  user: example_user
+  vhost: /
+status: {}
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Policy/health_test.yaml b/resource_customizations/rabbitmq.com/Policy/health_test.yaml
new file mode 100644
index 000000000..238f95229
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Policy/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ policy failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ policy status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ policy ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Policy/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Policy/testdata/degraded.yaml
new file mode 100644
index 000000000..f6b5466d6
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Policy/testdata/degraded.yaml
@@ -0,0 +1,21 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Policy
+metadata:
+  namespace: example-rabbit
+  name: example-rabbit
+spec:
+  name: example-rabbit
+  vhost: example-rabbit
+  pattern: .*
+  applyTo: queues
+  definition: {}
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: example-rabbit
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Policy/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Policy/testdata/healthy.yaml
new file mode 100644
index 000000000..7ce4962a2
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Policy/testdata/healthy.yaml
@@ -0,0 +1,21 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Policy
+metadata:
+  namespace: example-rabbit
+  name: example-rabbit
+spec:
+  name: example-rabbit
+  vhost: example-rabbit
+  pattern: .*
+  applyTo: queues
+  definition: {}
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: example-rabbit
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Policy/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Policy/testdata/unknown.yaml
new file mode 100644
index 000000000..e66dec82c
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Policy/testdata/unknown.yaml
@@ -0,0 +1,15 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Policy
+metadata:
+  namespace: example-rabbit
+  name: example-rabbit
+spec:
+  name: example-rabbit
+  vhost: example-rabbit
+  pattern: .*
+  applyTo: queues
+  definition: {}
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: example-rabbit
+status: {}
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Queue/health_test.yaml b/resource_customizations/rabbitmq.com/Queue/health_test.yaml
new file mode 100644
index 000000000..c756cb5f3
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Queue/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ queue failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ queue status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ queue ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Queue/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Queue/testdata/degraded.yaml
new file mode 100644
index 000000000..fd67dbb25
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Queue/testdata/degraded.yaml
@@ -0,0 +1,21 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Queue
+metadata:
+  generation: 3
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  autoDelete: false
+  durable: true
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+  type: quorum
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 3
diff --git a/resource_customizations/rabbitmq.com/Queue/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Queue/testdata/healthy.yaml
new file mode 100644
index 000000000..dda57ec06
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Queue/testdata/healthy.yaml
@@ -0,0 +1,21 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Queue
+metadata:
+  generation: 3
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  autoDelete: false
+  durable: true
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+  type: quorum
+  vhost: /
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 3
diff --git a/resource_customizations/rabbitmq.com/Queue/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Queue/testdata/unknown.yaml
new file mode 100644
index 000000000..50fb3e045
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Queue/testdata/unknown.yaml
@@ -0,0 +1,15 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Queue
+metadata:
+  generation: 3
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  autoDelete: false
+  durable: true
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+  type: quorum
+  vhost: /
+status: {}
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Shovel/health_test.yaml b/resource_customizations/rabbitmq.com/Shovel/health_test.yaml
new file mode 100644
index 000000000..74fdef335
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Shovel/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ shovel failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ shovel status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ shovel ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Shovel/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Shovel/testdata/degraded.yaml
new file mode 100644
index 000000000..3d3286359
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Shovel/testdata/degraded.yaml
@@ -0,0 +1,15 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Shovel
+metadata:
+  namespace: example-rabbit
+  name: example-rabbit
+spec:
+  name: example-rabbit
+  vhost: example-rabbit
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Shovel/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Shovel/testdata/healthy.yaml
new file mode 100644
index 000000000..d4e78ec75
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Shovel/testdata/healthy.yaml
@@ -0,0 +1,15 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Shovel
+metadata:
+  namespace: example-rabbit
+  name: example-rabbit
+spec:
+  name: example-rabbit
+  vhost: example-rabbit
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Shovel/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Shovel/testdata/unknown.yaml
new file mode 100644
index 000000000..41e7c9608
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Shovel/testdata/unknown.yaml
@@ -0,0 +1,9 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Shovel
+metadata:
+  namespace: example-rabbit
+  name: example-rabbit
+spec:
+  name: example-rabbit
+  vhost: example-rabbit
+status: {}
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/User/health_test.yaml b/resource_customizations/rabbitmq.com/User/health_test.yaml
new file mode 100644
index 000000000..9769a57e5
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/User/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ user failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ user status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ user ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/User/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/User/testdata/degraded.yaml
new file mode 100644
index 000000000..869b33438
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/User/testdata/degraded.yaml
@@ -0,0 +1,24 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: User
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: exmple-rabbit
+spec:
+  importCredentialsSecret:
+    name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: exmple-rabbit
+  tags:
+    - administrator
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  credentials:
+    name: example-rabbit-user-credentials
+  observedGeneration: 1
+  username: example_user
diff --git a/resource_customizations/rabbitmq.com/User/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/User/testdata/healthy.yaml
new file mode 100644
index 000000000..416f01e73
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/User/testdata/healthy.yaml
@@ -0,0 +1,24 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: User
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: exmple-rabbit
+spec:
+  importCredentialsSecret:
+    name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: exmple-rabbit
+  tags:
+    - administrator
+status:
+  conditions:
+    - lastTransitionTime: 2025-01-10T20:48:01Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  credentials:
+    name: example-rabbit-user-credentials
+  observedGeneration: 1
+  username: example_user
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/User/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/User/testdata/unknown.yaml
new file mode 100644
index 000000000..0614ec179
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/User/testdata/unknown.yaml
@@ -0,0 +1,25 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: User
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: exmple-rabbit
+spec:
+  importCredentialsSecret:
+    name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: exmple-rabbit
+  tags:
+    - administrator
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  credentials:
+    name: example-rabbit-user-credentials
+  observedGeneration: 1
+  username: example_user
+status: {}
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Vhost/health_test.yaml b/resource_customizations/rabbitmq.com/Vhost/health_test.yaml
new file mode 100644
index 000000000..ed713d59f
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Vhost/health_test.yaml
@@ -0,0 +1,13 @@
+tests:
+- healthStatus:
+    status: Degraded
+    message: RabbitMQ vhost failed to be created or updated
+  inputPath: testdata/degraded.yaml
+- healthStatus:
+    status: Unknown
+    message: RabbitMQ vhost status is unknown
+  inputPath: testdata/unknown.yaml
+- healthStatus:
+    status: Healthy
+    message: RabbitMQ vhost ready
+  inputPath: testdata/healthy.yaml
\ No newline at end of file
diff --git a/resource_customizations/rabbitmq.com/Vhost/testdata/degraded.yaml b/resource_customizations/rabbitmq.com/Vhost/testdata/degraded.yaml
new file mode 100644
index 000000000..68bc6e3fa
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Vhost/testdata/degraded.yaml
@@ -0,0 +1,19 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Vhost
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  defaultQueueType: quorum
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: example-rabbit
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: FailedCreateOrUpdate
+      status: "False"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Vhost/testdata/healthy.yaml b/resource_customizations/rabbitmq.com/Vhost/testdata/healthy.yaml
new file mode 100644
index 000000000..d2e82c8fd
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Vhost/testdata/healthy.yaml
@@ -0,0 +1,19 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Vhost
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  defaultQueueType: quorum
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: example-rabbit
+status:
+  conditions:
+    - lastTransitionTime: 2025-02-24T17:51:10Z
+      reason: SuccessfulCreateOrUpdate
+      status: "True"
+      type: Ready
+  observedGeneration: 1
diff --git a/resource_customizations/rabbitmq.com/Vhost/testdata/unknown.yaml b/resource_customizations/rabbitmq.com/Vhost/testdata/unknown.yaml
new file mode 100644
index 000000000..adc5ca7f0
--- /dev/null
+++ b/resource_customizations/rabbitmq.com/Vhost/testdata/unknown.yaml
@@ -0,0 +1,13 @@
+apiVersion: rabbitmq.com/v1beta1
+kind: Vhost
+metadata:
+  generation: 1
+  name: example-rabbit
+  namespace: example-rabbit
+spec:
+  defaultQueueType: quorum
+  name: example-rabbit
+  rabbitmqClusterReference:
+    name: example-rabbit
+    namespace: example-rabbit
+status: {}
\ No newline at end of file
diff --git a/test/remote/Dockerfile b/test/remote/Dockerfile
index a220616b0..6da0a30c9 100644
--- a/test/remote/Dockerfile
+++ b/test/remote/Dockerfile
@@ -1,6 +1,6 @@
 ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab
 
-FROM docker.io/library/golang:1.24.3@sha256:86b4cff66e04d41821a17cea30c1031ed53e2635e2be99ae0b4a7d69336b5063 AS go
+FROM docker.io/library/golang:1.24.3@sha256:1bcf8844d2464a6485c87646f9da684610758eb1a2df63c8a6e7ca47c64f8655 AS go
 
 RUN go install github.com/mattn/goreman@latest && \
     go install github.com/kisielk/godepgraph@latest
diff --git a/util/grpc/useragent_test.go b/util/grpc/useragent_test.go
index 3585a6b41..8716aa696 100644
--- a/util/grpc/useragent_test.go
+++ b/util/grpc/useragent_test.go
@@ -9,49 +9,36 @@ import (
 )
 
 func Test_UserAgentEnforcer(t *testing.T) {
+	clientName := "argo-cd"
+	semverConstraint, _ := semver.NewConstraint("^1")
 	t.Run("Test enforcing valid user-agent", func(t *testing.T) {
-		clientName := "argo-cd"
-		constraintStr := "^1"
-		semverConstraint, _ := semver.NewConstraint(constraintStr)
 		md := metadata.New(map[string]string{"user-agent": "argo-cd/1.0"})
 		ctx := metadata.NewIncomingContext(t.Context(), md)
-		err := userAgentEnforcer(ctx, clientName, constraintStr, semverConstraint)
+		err := userAgentEnforcer(ctx, clientName, semverConstraint)
 		require.NoError(t, err)
 	})
 	t.Run("Test enforcing ignored user-agent", func(t *testing.T) {
-		clientName := "argo-cd"
-		constraintStr := "^1"
-		semverConstraint, _ := semver.NewConstraint(constraintStr)
 		md := metadata.New(map[string]string{"user-agent": "flux/3.0"})
 		ctx := metadata.NewIncomingContext(t.Context(), md)
-		err := userAgentEnforcer(ctx, clientName, constraintStr, semverConstraint)
+		err := userAgentEnforcer(ctx, clientName, semverConstraint)
 		require.NoError(t, err)
 	})
 	t.Run("Test enforcing user-agent with version not matching constraint", func(t *testing.T) {
-		clientName := "argo-cd"
-		constraintStr := "^1"
-		semverConstraint, _ := semver.NewConstraint(constraintStr)
 		md := metadata.New(map[string]string{"user-agent": "argo-cd/3.0"})
 		ctx := metadata.NewIncomingContext(t.Context(), md)
-		err := userAgentEnforcer(ctx, clientName, constraintStr, semverConstraint)
+		err := userAgentEnforcer(ctx, clientName, semverConstraint)
 		require.ErrorContains(t, err, "unsatisfied client version constraint")
 	})
 	t.Run("Test legacy user-agent", func(t *testing.T) {
-		clientName := "argo-cd"
-		constraintStr := "^1"
-		semverConstraint, _ := semver.NewConstraint(constraintStr)
 		md := metadata.New(map[string]string{"user-agent": "grpc-go/1.15.0"})
 		ctx := metadata.NewIncomingContext(t.Context(), md)
-		err := userAgentEnforcer(ctx, clientName, constraintStr, semverConstraint)
-		require.ErrorContains(t, err, "unsatisfied client version constraint")
+		err := userAgentEnforcer(ctx, clientName, semverConstraint)
+		require.ErrorContains(t, err, "unsatisfied client version constraint: ^1")
 	})
 	t.Run("Test invalid version", func(t *testing.T) {
-		clientName := "argo-cd"
-		constraintStr := "^1"
-		semverConstraint, _ := semver.NewConstraint(constraintStr)
 		md := metadata.New(map[string]string{"user-agent": "argo-cd/super"})
 		ctx := metadata.NewIncomingContext(t.Context(), md)
-		err := userAgentEnforcer(ctx, clientName, constraintStr, semverConstraint)
+		err := userAgentEnforcer(ctx, clientName, semverConstraint)
 		require.ErrorContains(t, err, "could not parse version")
 	})
 }
diff --git a/util/lua/lua_test.go b/util/lua/lua_test.go
index aa7842410..9274d279d 100644
--- a/util/lua/lua_test.go
+++ b/util/lua/lua_test.go
@@ -284,8 +284,7 @@ func TestGetResourceActionNoPredefined(t *testing.T) {
 	testObj := StrToUnstructured(objWithNoScriptJSON)
 	vm := VM{}
 	action, err := vm.GetResourceAction(testObj, "test")
-	var expectedErr *ScriptDoesNotExistError
-	require.ErrorAs(t, err, &expectedErr)
+	require.ErrorIs(t, err, errScriptDoesNotExist)
 	assert.Empty(t, action.ActionLua)
 }
 
@@ -868,7 +867,7 @@ return hs`
 	})
 
 	t.Run("Get resource health for */* override with empty health.lua", func(t *testing.T) {
-		testObj := StrToUnstructured(ec2AWSCrossplaneObjJSON)
+		testObj := StrToUnstructured(objWithNoScriptJSON)
 		overrides := getBaseWildcardHealthOverrides
 		status, err := overrides.GetResourceHealth(testObj)
 		require.NoError(t, err)
@@ -954,3 +953,12 @@ func createMockResource(kind string, name string, replicas int) *unstructured.Un
             image: nginx
     `, kind, name, replicas))
 }
+
+func Test_getHealthScriptPaths(t *testing.T) {
+	paths, err := getGlobHealthScriptPaths()
+	require.NoError(t, err)
+
+	// This test will fail any time a glob pattern is added to the health script paths. We don't expect that to happen
+	// often.
+	assert.Equal(t, []string{"_.crossplane.io/_", "_.upbound.io/_"}, paths)
+}