Repository: databus23/helm-diff

Issue:
Helm 4 compatibility
When using the plugin with Helm 4, I have the following error:

```console
Error: Failed to render chart: exit status 1: Flag --validate has been deprecated, use '--dry-run=server' instead
Error: if any flags in the group [validate dry-run] are set none of the others can be; [dry-run validate] were all set

Error: plugin "diff" exited with error
```

Discussion:

Solution patch:
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 40667dc..be06c72 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -9,12 +9,10 @@ Always reference these instructions first and fallback to search or bash command
 **Prerequisites:**
 - Go >= 1.21 (currently uses Go 1.24.5)
 - Helm v3 (tested with v3.17.4 and v3.18.6)
-- Make sure `/home/runner/go/bin` is in your PATH for staticcheck: `export PATH=$PATH:/home/runner/go/bin`
 
-**Bootstrap and Build Process:**
-- ALWAYS run: `make bootstrap` first - downloads dependencies and installs staticcheck. Takes <1 second (if already done) or ~50 seconds (first time).
-- Build the plugin: `make build` - includes linting and compiles the binary. Takes ~9 seconds after bootstrap.
-- NEVER CANCEL builds. Set timeout to 3+ minutes for bootstrap, 2+ minutes for build operations.
+**Build Process:**
+- Build the plugin: `make build` - includes linting and compiles the binary.
+- NEVER CANCEL builds. Set timeout to 2+ minutes for build operations.
 
 **Testing:**
 - Run unit tests: `make test` - includes coverage analysis. Takes ~12 seconds. NEVER CANCEL - set timeout to 3+ minutes.
@@ -22,13 +20,13 @@ Always reference these instructions first and fallback to search or bash command
 - Test coverage is generated in `cover.out` with detailed function-level coverage reports.
 
 **Linting and Code Quality:**
-- Local linting: `make lint` - runs gofmt, go vet, and staticcheck verification. Takes ~2 seconds.
+- Local linting: `make lint` - runs gofmt, go vet. Takes ~2 seconds.
 - Code formatting: `make format` - applies gofmt formatting automatically. Takes <1 second.
 - Full golangci-lint runs only in CI via GitHub Actions, not available locally.
 - ALWAYS run `make format` and `make lint` before committing changes.
 
 **Plugin Installation:**
-- Install as Helm plugin: `make install` or `make install/helm3` - builds and installs to Helm plugins directory. Takes ~3 seconds.
+- Install as Helm plugin: `make install` or `make install/helm` - builds and installs to Helm plugins directory. Takes ~3 seconds.
 - The plugin installs via `install-binary.sh` script which handles cross-platform binary installation.
 
 ## Validation Scenarios
@@ -63,8 +61,7 @@ Always reference these instructions first and fallback to search or bash command
 
 **CRITICAL: NEVER CANCEL long-running commands. Use these timeout values:**
 
-- `make bootstrap`: <1 second (if already done) or ~50 seconds (first time) (set timeout: 5+ minutes)
-- `make build`: ~9 seconds after bootstrap (set timeout: 3+ minutes)
+- `make build`: ~9 seconds (set timeout: 3+ minutes)
 - `make test`: ~12 seconds (set timeout: 3+ minutes)
 - `make lint`: ~2 seconds (set timeout: 1 minute)
 - `make format`: <1 second (set timeout: 1 minute)
@@ -77,7 +74,7 @@ Always reference these instructions first and fallback to search or bash command
 - `cmd/` - Command-line interface implementation (upgrade, release, revision, rollback, version)
 - `diff/` - Core diffing logic and output formatting
 - `manifest/` - Kubernetes manifest parsing and handling
-- `scripts/` - Build and verification scripts (gofmt, govet, staticcheck)
+- `scripts/` - Build and verification scripts (gofmt, govet)
 - `testdata/`, `*/testdata/` - Test fixtures and mock data
 - `plugin.yaml` - Helm plugin configuration
 - `install-binary.sh` - Cross-platform installation script
@@ -114,7 +111,6 @@ HELM_NAMESPACE=default HELM_BIN=helm ./bin/diff upgrade --install --dry-run my-r
 **Common Commands Reference:**
 ```bash
 # Full development cycle
-make bootstrap      # Install dependencies (once)
 make build         # Build with linting  
 make test          # Run all tests
 make format        # Format code
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a54a6eb..3648934 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -16,10 +16,7 @@ jobs:
       - uses: actions/checkout@v5
       - uses: actions/setup-go@v6
         with:
-          go-version-file: 'go.mod'
-
-      - name: Install dependencies
-        run: make bootstrap
+          go-version-file: "go.mod"
 
       - name: Run unit tests
         run: make test
@@ -41,9 +38,9 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        shell: [ default ]
-        experimental: [ false ]
-        helm-version: [ v3.18.6, v3.19.0 ]
+        shell: [default]
+        experimental: [false]
+        helm-version: [v3.18.6, v3.19.2, v4.0.0]
         include:
           - os: windows-latest
             shell: wsl
@@ -61,16 +58,29 @@ jobs:
           - os: windows-latest
             shell: wsl
             experimental: false
-            helm-version: v3.19.0
+            helm-version: v3.19.2
+          - os: windows-latest
+            shell: cygwin
+            experimental: false
+            helm-version: v3.19.2
+          - os: ubuntu-latest
+            container: alpine
+            shell: sh
+            experimental: false
+            helm-version: v3.19.2
+          - os: windows-latest
+            shell: wsl
+            experimental: false
+            helm-version: v4.0.0
           - os: windows-latest
             shell: cygwin
             experimental: false
-            helm-version: v3.19.0
+            helm-version: v4.0.0
           - os: ubuntu-latest
             container: alpine
             shell: sh
             experimental: false
-            helm-version: v3.19.0
+            helm-version: v4.0.0
 
     steps:
       - name: Disable autocrlf
@@ -84,7 +94,7 @@ jobs:
       - name: Setup Helm
         uses: azure/setup-helm@v4
         with:
-          version: ${{ matrix.helm-version }}
+          version: "${{ matrix.helm-version }}"
 
       - name: Setup WSL
         if: "contains(matrix.shell, 'wsl')"
@@ -95,7 +105,7 @@ jobs:
         uses: egor-tensin/setup-cygwin@v4
 
       - name: helm plugin install
-        run: helm plugin install .
+        run: helm plugin install --debug .
 
   integration-tests:
     name: Integration Tests
@@ -109,12 +119,12 @@ jobs:
           # That's why we cover only 2 Helm minor versions in this matrix.
           # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context.
           - helm-version: v3.18.6
-          - helm-version: v3.19.0
+          - helm-version: v3.19.2
+          - helm-version: v4.0.0
     steps:
       - uses: engineerd/setup-kind@v0.6.2
         with:
-          skipClusterLogsExport: true 
-
+          skipClusterLogsExport: true
 
       - uses: actions/checkout@v5
 
@@ -123,8 +133,11 @@ jobs:
         with:
           version: ${{ matrix.helm-version }}
 
+      - name: Helm Version
+        run: helm version
+
       - name: helm plugin install
-        run: helm plugin install .
+        run: make install/helm
 
       - name: helm create helm-diff
         run: helm create helm-diff
diff --git a/.golangci.yaml b/.golangci.yaml
index 1338907..831c699 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -58,7 +58,7 @@ linters:
             - golang.org/x/term
             - gopkg.in/yaml.v2
             - github.com/stretchr/testify/require
-            - helm.sh/helm/v3
+            - helm.sh/helm/v4
             - k8s.io/api/core/v1
             - k8s.io/apiextensions-apiserver
             - k8s.io/apimachinery
diff --git a/Makefile b/Makefile
index 86ffbfb..ff362c5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 HELM_HOME ?= $(shell helm env HELM_DATA_HOME)
 VERSION := $(shell sed -n -e 's/version:[ "]*\([^"]*\).*/\1/p' plugin.yaml)
 
-HELM_3_PLUGINS := $(shell helm env HELM_PLUGINS)
+HELM_PLUGINS := $(shell helm env HELM_PLUGINS)
 
 PKG:= github.com/databus23/helm-diff/v3
 LDFLAGS := -X $(PKG)/cmd.Version=$(VERSION)
@@ -19,18 +19,17 @@ install: build
 	cp bin/diff $(HELM_HOME)/plugins/helm-diff/bin
 	cp plugin.yaml $(HELM_HOME)/plugins/helm-diff/
 
-.PHONY: install/helm3
-install/helm3: build
-	mkdir -p $(HELM_3_PLUGINS)/helm-diff/bin
-	cp bin/diff $(HELM_3_PLUGINS)/helm-diff/bin
-	cp plugin.yaml $(HELM_3_PLUGINS)/helm-diff/
+.PHONY: install/helm
+install/helm: build
+	mkdir -p $(HELM_PLUGINS)/helm-diff/bin
+	cp bin/diff $(HELM_PLUGINS)/helm-diff/bin
+	cp plugin.yaml $(HELM_PLUGINS)/helm-diff/
 
 .PHONY: lint
 lint:
 	scripts/update-gofmt.sh
 	scripts/verify-gofmt.sh
 	scripts/verify-govet.sh
-	scripts/verify-staticcheck.sh
 
 .PHONY: build
 build: lint
@@ -42,11 +41,6 @@ test:
 	go test -v ./... -coverprofile cover.out -race
 	go tool cover -func cover.out
 
-.PHONY: bootstrap
-bootstrap:
-	go mod download
-	command -v staticcheck || go install honnef.co/go/tools/cmd/staticcheck@latest
-
 .PHONY: docker-run-release
 docker-run-release: export pkg=/go/src/github.com/databus23/helm-diff
 docker-run-release:
@@ -63,7 +57,7 @@ docker-run-release:
 	-v ${SSH_AUTH_SOCK}:/tmp/ssh-agent.sock -e SSH_AUTH_SOCK=/tmp/ssh-agent.sock \
 	-v $(shell pwd):$(pkg) \
 	-v $(shell pwd)/docker-run-release-cache:/.cache \
-	-w $(pkg) helm-diff-release make bootstrap release
+	-w $(pkg) helm-diff-release make release
 
 .PHONY: dist
 dist: export COPYFILE_DISABLE=1 #teach OSX tar to not put ._* files in tar archive
diff --git a/README.md b/README.md
index 5fcde05..d7e9124 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ The first step is to download the repository and enter the directory. You can do
 Next, install the plugin into helm.
 
 ```bash
-make install/helm3
+make install/helm
 ```
 
 
diff --git a/cmd/helm.go b/cmd/helm.go
index 8802ce5..55d3cce 100644
--- a/cmd/helm.go
+++ b/cmd/helm.go
@@ -1,16 +1,20 @@
 package cmd
 
-// This file contains functions that where blatantly copied from
-// https://github.wdf.sap.corp/kubernetes/helm
-
 import (
+	"bytes"
 	"errors"
 	"fmt"
+	"io"
 	"os"
+	"os/exec"
+	"regexp"
+	"strconv"
 	"strings"
+
+	"github.com/Masterminds/semver/v3"
 )
 
-/////////////// Source: cmd/helm/install.go /////////////////////////
+// Source: cmd/helm/install.go
 
 type valueFiles []string
 
@@ -47,7 +51,7 @@ func (v *valueFiles) Set(value string) error {
 	return nil
 }
 
-/////////////// Source: cmd/helm/helm.go ////////////////////////////
+// Source: cmd/helm/helm.go
 
 func checkArgsLength(argsReceived int, requiredArgs ...string) error {
 	expectedNum := len(requiredArgs)
@@ -60,3 +64,409 @@ func checkArgsLength(argsReceived int, requiredArgs ...string) error {
 	}
 	return nil
 }
+
+var (
+	helmVersionRE  = regexp.MustCompile(`Version:\s*"([^"]+)"`)
+	helmV4Version  = semver.MustParse("v4.0.0")
+	minHelmVersion = semver.MustParse("v3.1.0-rc.1")
+	// See https://github.com/helm/helm/pull/9426.
+	minHelmVersionWithDryRunLookupSupport = semver.MustParse("v3.13.0")
+	// The --reset-then-reuse-values flag for `helm upgrade` was added in
+	// https://github.com/helm/helm/pull/9653 and released as part of Helm v3.14.0.
+	minHelmVersionWithResetThenReuseValues = semver.MustParse("v3.14.0")
+)
+
+func getHelmVersion() (*semver.Version, error) {
+	cmd := exec.Command(os.Getenv("HELM_BIN"), "version")
+	debugPrint("Executing %s", strings.Join(cmd.Args, " "))
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to run `%s version`: %w", os.Getenv("HELM_BIN"), err)
+	}
+	versionOutput := string(output)
+
+	matches := helmVersionRE.FindStringSubmatch(versionOutput)
+	if matches == nil {
+		return nil, fmt.Errorf("Failed to find version in output %#v", versionOutput)
+	}
+	helmVersion, err := semver.NewVersion(matches[1])
+	if err != nil {
+		return nil, fmt.Errorf("Failed to parse version %#v: %w", matches[1], err)
+	}
+
+	return helmVersion, nil
+}
+
+func isHelmVersionAtLeast(versionToCompareTo *semver.Version) (bool, error) {
+	helmVersion, err := getHelmVersion()
+
+	if err != nil {
+		return false, err
+	}
+	if helmVersion.LessThan(versionToCompareTo) {
+		return false, nil
+	}
+	return true, nil
+}
+
+func isHelmVersionGreaterThanEqual(versionToCompareTo *semver.Version) (bool, error) {
+	helmVersion, err := getHelmVersion()
+
+	if err != nil {
+		return false, err
+	}
+
+	return helmVersion.GreaterThanEqual(versionToCompareTo), nil
+}
+
+func compatibleHelm3Version() error {
+	isCompatible, err := isHelmVersionAtLeast(minHelmVersion)
+	if err != nil {
+		return err
+	}
+	if !isCompatible {
+		return fmt.Errorf("helm diff upgrade requires at least helm version %s", minHelmVersion.String())
+	}
+	return nil
+}
+
+func getRelease(release, namespace string) ([]byte, error) {
+	args := []string{"get", "manifest", release}
+	if namespace != "" {
+		args = append(args, "--namespace", namespace)
+	}
+	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
+	return outputWithRichError(cmd)
+}
+
+func getHooks(release, namespace string) ([]byte, error) {
+	args := []string{"get", "hooks", release}
+	if namespace != "" {
+		args = append(args, "--namespace", namespace)
+	}
+	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
+	return outputWithRichError(cmd)
+}
+
+func getRevision(release string, revision int, namespace string) ([]byte, error) {
+	args := []string{"get", "manifest", release, "--revision", strconv.Itoa(revision)}
+	if namespace != "" {
+		args = append(args, "--namespace", namespace)
+	}
+	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
+	return outputWithRichError(cmd)
+}
+
+func getChart(release, namespace string) (string, error) {
+	args := []string{"get", "all", release, "--template", "{{.Release.Chart.Name}}"}
+	if namespace != "" {
+		args = append(args, "--namespace", namespace)
+	}
+	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
+	out, err := outputWithRichError(cmd)
+	if err != nil {
+		return "", err
+	}
+	return string(out), nil
+}
+
+func (d *diffCmd) template(isUpgrade bool) ([]byte, error) {
+	flags := []string{}
+	if d.devel {
+		flags = append(flags, "--devel")
+	}
+	if d.noHooks && !d.useUpgradeDryRun {
+		flags = append(flags, "--no-hooks")
+	}
+	if d.includeCRDs {
+		flags = append(flags, "--include-crds")
+	}
+	if d.chartVersion != "" {
+		flags = append(flags, "--version", d.chartVersion)
+	}
+	if d.chartRepo != "" {
+		flags = append(flags, "--repo", d.chartRepo)
+	}
+	if d.namespace != "" {
+		flags = append(flags, "--namespace", d.namespace)
+	}
+	if d.postRenderer != "" {
+		flags = append(flags, "--post-renderer", d.postRenderer)
+	}
+	for _, arg := range d.postRendererArgs {
+		flags = append(flags, "--post-renderer-args", arg)
+	}
+	if d.insecureSkipTLSVerify {
+		flags = append(flags, "--insecure-skip-tls-verify")
+	}
+	// Helm automatically enable --reuse-values when there's no --set, --set-string, --set-json, --set-values, --set-file present.
+	// Let's simulate that in helm-diff.
+	// See https://medium.com/@kcatstack/understand-helm-upgrade-flags-reset-values-reuse-values-6e58ac8f127e
+	shouldDefaultReusingValues := isUpgrade && len(d.values) == 0 && len(d.stringValues) == 0 && len(d.stringLiteralValues) == 0 && len(d.jsonValues) == 0 && len(d.valueFiles) == 0 && len(d.fileValues) == 0
+	if (d.reuseValues || d.resetThenReuseValues || shouldDefaultReusingValues) && !d.resetValues && d.clusterAccessAllowed() {
+		tmpfile, err := os.CreateTemp("", "existing-values")
+		if err != nil {
+			return nil, err
+		}
+		defer func() {
+			_ = os.Remove(tmpfile.Name())
+		}()
+		// In the presence of --reuse-values (or --reset-values), --reset-then-reuse-values is ignored.
+		if d.resetThenReuseValues && !d.reuseValues {
+			var supported bool
+			supported, err = isHelmVersionAtLeast(minHelmVersionWithResetThenReuseValues)
+			if err != nil {
+				return nil, err
+			}
+			if !supported {
+				return nil, fmt.Errorf("Using --reset-then-reuse-values requires at least helm version %s", minHelmVersionWithResetThenReuseValues.String())
+			}
+			err = d.writeExistingValues(tmpfile, false)
+		} else {
+			err = d.writeExistingValues(tmpfile, true)
+		}
+		if err != nil {
+			return nil, err
+		}
+		flags = append(flags, "--values", tmpfile.Name())
+	}
+	for _, value := range d.values {
+		flags = append(flags, "--set", value)
+	}
+	for _, stringValue := range d.stringValues {
+		flags = append(flags, "--set-string", stringValue)
+	}
+	for _, stringLiteralValue := range d.stringLiteralValues {
+		flags = append(flags, "--set-literal", stringLiteralValue)
+	}
+	for _, jsonValue := range d.jsonValues {
+		flags = append(flags, "--set-json", jsonValue)
+	}
+	for _, valueFile := range d.valueFiles {
+		if strings.TrimSpace(valueFile) == "-" {
+			bytes, err := io.ReadAll(os.Stdin)
+			if err != nil {
+				return nil, err
+			}
+
+			tmpfile, err := os.CreateTemp("", "helm-diff-stdin-values")
+			if err != nil {
+				return nil, err
+			}
+			defer func() {
+				_ = os.Remove(tmpfile.Name())
+			}()
+
+			if _, err := tmpfile.Write(bytes); err != nil {
+				_ = tmpfile.Close()
+				return nil, err
+			}
+
+			if err := tmpfile.Close(); err != nil {
+				return nil, err
+			}
+
+			flags = append(flags, "--values", tmpfile.Name())
+		} else {
+			flags = append(flags, "--values", valueFile)
+		}
+	}
+	for _, fileValue := range d.fileValues {
+		flags = append(flags, "--set-file", fileValue)
+	}
+
+	if d.disableOpenAPIValidation {
+		flags = append(flags, "--disable-openapi-validation")
+	}
+
+	if d.enableDNS {
+		flags = append(flags, "--enable-dns")
+	}
+
+	if d.SkipSchemaValidation {
+		flags = append(flags, "--skip-schema-validation")
+	}
+
+	if d.takeOwnership {
+		flags = append(flags, "--take-ownership")
+	}
+
+	var (
+		subcmd string
+		filter func([]byte) []byte
+	)
+
+	// `--dry-run=client` or `--dry-run=server`?
+	//
+	// Or what's the relationoship between helm-diff's --dry-run flag,
+	// HELM_DIFF_UPGRADE_DRY_RUN env var and the helm upgrade --dry-run flag?
+	//
+	// Read on to find out.
+	if d.useUpgradeDryRun {
+		if d.isAllowUnreleased() {
+			// Otherwise you get the following error when this is a diff for a new install
+			//   Error: UPGRADE FAILED: "$RELEASE_NAME" has no deployed releases
+			flags = append(flags, "--install")
+		}
+
+		// If the program reaches here,
+		// we are sure that the user wants to use the `helm upgrade --dry-run` command
+		// for generating the manifests to be diffed.
+		//
+		// So the question is only whether to use `--dry-run=client` or `--dry-run=server`.
+		//
+		// As HELM_DIFF_UPGRADE_DRY_RUN is there for producing more complete and correct diff results,
+		// we use --dry-run=server if the version of helm supports it.
+		// Otherwise, we use --dry-run=client, as that's the best we can do.
+		if useDryRunService, err := isHelmVersionAtLeast(minHelmVersionWithDryRunLookupSupport); err == nil && useDryRunService {
+			flags = append(flags, "--dry-run=server")
+		} else {
+			flags = append(flags, "--dry-run")
+		}
+		subcmd = "upgrade"
+		filter = func(s []byte) []byte {
+			return extractManifestFromHelmUpgradeDryRunOutput(s, d.noHooks)
+		}
+	} else {
+		if !d.disableValidation && d.clusterAccessAllowed() {
+			isHelmV4, err := isHelmVersionGreaterThanEqual(helmV4Version)
+			if err == nil && isHelmV4 {
+				// Flag --validate has been deprecated, use '--dry-run=server' instead in Helm v4+
+				flags = append(flags, "--dry-run=server")
+			} else {
+				flags = append(flags, "--validate")
+			}
+		}
+
+		if isUpgrade {
+			flags = append(flags, "--is-upgrade")
+		}
+
+		for _, a := range d.extraAPIs {
+			flags = append(flags, "--api-versions", a)
+		}
+
+		if d.kubeVersion != "" {
+			flags = append(flags, "--kube-version", d.kubeVersion)
+		}
+
+		// To keep the full compatibility with older helm-diff versions,
+		// we pass --dry-run to `helm template` only if Helm is greater than v3.13.0.
+		if useDryRunService, err := isHelmVersionAtLeast(minHelmVersionWithDryRunLookupSupport); err == nil && useDryRunService {
+			// However, which dry-run mode to use is still not clear.
+			//
+			// For compatibility with the old and new helm-diff options,
+			// old and new helm, we assume that the user wants to use the older `helm template --dry-run=client` mode
+			// if helm-diff has been invoked with any of the following flags:
+			//
+			// * no dry-run flags (to be consistent with helm-template)
+			// * --dry-run
+			// * --dry-run=""
+			// * --dry-run=client
+			//
+			// and the newer `helm template --dry-run=server` mode when invoked with:
+			//
+			// * --dry-run=server
+			//
+			// Any other values should result in errors.
+			//
+			// See the fllowing link for more details:
+			// - https://github.com/databus23/helm-diff/pull/458
+			// - https://github.com/helm/helm/pull/9426#issuecomment-1501005666
+			if d.dryRunMode == "server" {
+				// This is for security reasons!
+				//
+				// We give helm-template the additional cluster access for the helm `lookup` function
+				// only if the user has explicitly requested it by --dry-run=server,
+				//
+				// In other words, although helm-diff-upgrade implies limited cluster access by default,
+				// helm-diff-upgrade without a --dry-run flag does NOT imply
+				// full cluster-access via helm-template --dry-run=server!
+				flags = append(flags, "--dry-run=server")
+			} else {
+				// Since helm-diff 3.9.0 and helm 3.13.0, we pass --dry-run=client to `helm template` by default.
+				// This doesn't make any difference for helm-diff itself,
+				// because helm-template w/o flags is equivalent to helm-template --dry-run=client.
+				// See https://github.com/helm/helm/pull/9426#discussion_r1181397259
+				flags = append(flags, "--dry-run=client")
+			}
+		}
+
+		subcmd = "template"
+
+		filter = func(s []byte) []byte {
+			return s
+		}
+	}
+
+	args := []string{subcmd, d.release, d.chart}
+	args = append(args, flags...)
+
+	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
+	out, err := outputWithRichError(cmd)
+	return filter(out), err
+}
+
+func (d *diffCmd) writeExistingValues(f *os.File, all bool) error {
+	args := []string{"get", "values", d.release, "--output", "yaml"}
+	if all {
+		args = append(args, "--all")
+	}
+	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
+	debugPrint("Executing %s", strings.Join(cmd.Args, " "))
+	defer func() {
+		_ = f.Close()
+	}()
+	cmd.Stdout = f
+	return cmd.Run()
+}
+
+func extractManifestFromHelmUpgradeDryRunOutput(s []byte, noHooks bool) []byte {
+	if len(s) == 0 {
+		return s
+	}
+
+	var (
+		hooks    []byte
+		manifest []byte
+	)
+
+	i := bytes.Index(s, []byte("HOOKS:"))
+	if i > -1 {
+		hooks = s[i:]
+	}
+
+	j := bytes.Index(hooks, []byte("MANIFEST:"))
+	if j > -1 {
+		manifest = hooks[j:]
+		hooks = hooks[:j]
+	}
+
+	k := bytes.Index(manifest, []byte("\nNOTES:"))
+
+	if k > -1 {
+		manifest = manifest[:k+1]
+	}
+
+	if noHooks {
+		hooks = nil
+	} else {
+		a := bytes.Index(hooks, []byte("---"))
+		if a > -1 {
+			hooks = hooks[a:]
+		} else {
+			hooks = nil
+		}
+	}
+
+	a := bytes.Index(manifest, []byte("---"))
+	if a > -1 {
+		manifest = manifest[a:]
+	}
+
+	r := []byte{}
+	r = append(r, manifest...)
+	r = append(r, hooks...)
+
+	return r
+}
diff --git a/cmd/helm3.go b/cmd/helm3.go
deleted file mode 100644
index f624656..0000000
--- a/cmd/helm3.go
+++ /dev/null
@@ -1,401 +0,0 @@
-package cmd
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"os"
-	"os/exec"
-	"regexp"
-	"strconv"
-	"strings"
-
-	"github.com/Masterminds/semver/v3"
-)
-
-var (
-	helmVersionRE  = regexp.MustCompile(`Version:\s*"([^"]+)"`)
-	minHelmVersion = semver.MustParse("v3.1.0-rc.1")
-	// See https://github.com/helm/helm/pull/9426.
-	minHelmVersionWithDryRunLookupSupport = semver.MustParse("v3.13.0")
-	// The --reset-then-reuse-values flag for `helm upgrade` was added in
-	// https://github.com/helm/helm/pull/9653 and released as part of Helm v3.14.0.
-	minHelmVersionWithResetThenReuseValues = semver.MustParse("v3.14.0")
-)
-
-func getHelmVersion() (*semver.Version, error) {
-	cmd := exec.Command(os.Getenv("HELM_BIN"), "version")
-	debugPrint("Executing %s", strings.Join(cmd.Args, " "))
-	output, err := cmd.CombinedOutput()
-	if err != nil {
-		return nil, fmt.Errorf("Failed to run `%s version`: %w", os.Getenv("HELM_BIN"), err)
-	}
-	versionOutput := string(output)
-
-	matches := helmVersionRE.FindStringSubmatch(versionOutput)
-	if matches == nil {
-		return nil, fmt.Errorf("Failed to find version in output %#v", versionOutput)
-	}
-	helmVersion, err := semver.NewVersion(matches[1])
-	if err != nil {
-		return nil, fmt.Errorf("Failed to parse version %#v: %w", matches[1], err)
-	}
-
-	return helmVersion, nil
-}
-
-func isHelmVersionAtLeast(versionToCompareTo *semver.Version) (bool, error) {
-	helmVersion, err := getHelmVersion()
-
-	if err != nil {
-		return false, err
-	}
-	if helmVersion.LessThan(versionToCompareTo) {
-		return false, nil
-	}
-	return true, nil
-}
-
-func compatibleHelm3Version() error {
-	if isCompatible, err := isHelmVersionAtLeast(minHelmVersion); err != nil {
-		return err
-	} else if !isCompatible {
-		return fmt.Errorf("helm diff upgrade requires at least helm version %s", minHelmVersion.String())
-	}
-	return nil
-}
-
-func getRelease(release, namespace string) ([]byte, error) {
-	args := []string{"get", "manifest", release}
-	if namespace != "" {
-		args = append(args, "--namespace", namespace)
-	}
-	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
-	return outputWithRichError(cmd)
-}
-
-func getHooks(release, namespace string) ([]byte, error) {
-	args := []string{"get", "hooks", release}
-	if namespace != "" {
-		args = append(args, "--namespace", namespace)
-	}
-	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
-	return outputWithRichError(cmd)
-}
-
-func getRevision(release string, revision int, namespace string) ([]byte, error) {
-	args := []string{"get", "manifest", release, "--revision", strconv.Itoa(revision)}
-	if namespace != "" {
-		args = append(args, "--namespace", namespace)
-	}
-	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
-	return outputWithRichError(cmd)
-}
-
-func getChart(release, namespace string) (string, error) {
-	args := []string{"get", "all", release, "--template", "{{.Release.Chart.Name}}"}
-	if namespace != "" {
-		args = append(args, "--namespace", namespace)
-	}
-	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
-	out, err := outputWithRichError(cmd)
-	if err != nil {
-		return "", err
-	}
-	return string(out), nil
-}
-
-func (d *diffCmd) template(isUpgrade bool) ([]byte, error) {
-	flags := []string{}
-	if d.devel {
-		flags = append(flags, "--devel")
-	}
-	if d.noHooks && !d.useUpgradeDryRun {
-		flags = append(flags, "--no-hooks")
-	}
-	if d.includeCRDs {
-		flags = append(flags, "--include-crds")
-	}
-	if d.chartVersion != "" {
-		flags = append(flags, "--version", d.chartVersion)
-	}
-	if d.chartRepo != "" {
-		flags = append(flags, "--repo", d.chartRepo)
-	}
-	if d.namespace != "" {
-		flags = append(flags, "--namespace", d.namespace)
-	}
-	if d.postRenderer != "" {
-		flags = append(flags, "--post-renderer", d.postRenderer)
-	}
-	for _, arg := range d.postRendererArgs {
-		flags = append(flags, "--post-renderer-args", arg)
-	}
-	if d.insecureSkipTLSVerify {
-		flags = append(flags, "--insecure-skip-tls-verify")
-	}
-	// Helm automatically enable --reuse-values when there's no --set, --set-string, --set-json, --set-values, --set-file present.
-	// Let's simulate that in helm-diff.
-	// See https://medium.com/@kcatstack/understand-helm-upgrade-flags-reset-values-reuse-values-6e58ac8f127e
-	shouldDefaultReusingValues := isUpgrade && len(d.values) == 0 && len(d.stringValues) == 0 && len(d.stringLiteralValues) == 0 && len(d.jsonValues) == 0 && len(d.valueFiles) == 0 && len(d.fileValues) == 0
-	if (d.reuseValues || d.resetThenReuseValues || shouldDefaultReusingValues) && !d.resetValues && d.clusterAccessAllowed() {
-		tmpfile, err := os.CreateTemp("", "existing-values")
-		if err != nil {
-			return nil, err
-		}
-		defer func() {
-			_ = os.Remove(tmpfile.Name())
-		}()
-		// In the presence of --reuse-values (or --reset-values), --reset-then-reuse-values is ignored.
-		if d.resetThenReuseValues && !d.reuseValues {
-			var supported bool
-			supported, err = isHelmVersionAtLeast(minHelmVersionWithResetThenReuseValues)
-			if err != nil {
-				return nil, err
-			}
-			if !supported {
-				return nil, fmt.Errorf("Using --reset-then-reuse-values requires at least helm version %s", minHelmVersionWithResetThenReuseValues.String())
-			}
-			err = d.writeExistingValues(tmpfile, false)
-		} else {
-			err = d.writeExistingValues(tmpfile, true)
-		}
-		if err != nil {
-			return nil, err
-		}
-		flags = append(flags, "--values", tmpfile.Name())
-	}
-	for _, value := range d.values {
-		flags = append(flags, "--set", value)
-	}
-	for _, stringValue := range d.stringValues {
-		flags = append(flags, "--set-string", stringValue)
-	}
-	for _, stringLiteralValue := range d.stringLiteralValues {
-		flags = append(flags, "--set-literal", stringLiteralValue)
-	}
-	for _, jsonValue := range d.jsonValues {
-		flags = append(flags, "--set-json", jsonValue)
-	}
-	for _, valueFile := range d.valueFiles {
-		if strings.TrimSpace(valueFile) == "-" {
-			bytes, err := io.ReadAll(os.Stdin)
-			if err != nil {
-				return nil, err
-			}
-
-			tmpfile, err := os.CreateTemp("", "helm-diff-stdin-values")
-			if err != nil {
-				return nil, err
-			}
-			defer func() {
-				_ = os.Remove(tmpfile.Name())
-			}()
-
-			if _, err := tmpfile.Write(bytes); err != nil {
-				_ = tmpfile.Close()
-				return nil, err
-			}
-
-			if err := tmpfile.Close(); err != nil {
-				return nil, err
-			}
-
-			flags = append(flags, "--values", tmpfile.Name())
-		} else {
-			flags = append(flags, "--values", valueFile)
-		}
-	}
-	for _, fileValue := range d.fileValues {
-		flags = append(flags, "--set-file", fileValue)
-	}
-
-	if d.disableOpenAPIValidation {
-		flags = append(flags, "--disable-openapi-validation")
-	}
-
-	if d.enableDNS {
-		flags = append(flags, "--enable-dns")
-	}
-
-	if d.SkipSchemaValidation {
-		flags = append(flags, "--skip-schema-validation")
-	}
-
-	if d.takeOwnership {
-		flags = append(flags, "--take-ownership")
-	}
-
-	var (
-		subcmd string
-		filter func([]byte) []byte
-	)
-
-	// `--dry-run=client` or `--dry-run=server`?
-	//
-	// Or what's the relationoship between helm-diff's --dry-run flag,
-	// HELM_DIFF_UPGRADE_DRY_RUN env var and the helm upgrade --dry-run flag?
-	//
-	// Read on to find out.
-	if d.useUpgradeDryRun {
-		if d.isAllowUnreleased() {
-			// Otherwise you get the following error when this is a diff for a new install
-			//   Error: UPGRADE FAILED: "$RELEASE_NAME" has no deployed releases
-			flags = append(flags, "--install")
-		}
-
-		// If the program reaches here,
-		// we are sure that the user wants to use the `helm upgrade --dry-run` command
-		// for generating the manifests to be diffed.
-		//
-		// So the question is only whether to use `--dry-run=client` or `--dry-run=server`.
-		//
-		// As HELM_DIFF_UPGRADE_DRY_RUN is there for producing more complete and correct diff results,
-		// we use --dry-run=server if the version of helm supports it.
-		// Otherwise, we use --dry-run=client, as that's the best we can do.
-		if useDryRunService, err := isHelmVersionAtLeast(minHelmVersionWithDryRunLookupSupport); err == nil && useDryRunService {
-			flags = append(flags, "--dry-run=server")
-		} else {
-			flags = append(flags, "--dry-run")
-		}
-		subcmd = "upgrade"
-		filter = func(s []byte) []byte {
-			return extractManifestFromHelmUpgradeDryRunOutput(s, d.noHooks)
-		}
-	} else {
-		if !d.disableValidation && d.clusterAccessAllowed() {
-			flags = append(flags, "--validate")
-		}
-
-		if isUpgrade {
-			flags = append(flags, "--is-upgrade")
-		}
-
-		for _, a := range d.extraAPIs {
-			flags = append(flags, "--api-versions", a)
-		}
-
-		if d.kubeVersion != "" {
-			flags = append(flags, "--kube-version", d.kubeVersion)
-		}
-
-		// To keep the full compatibility with older helm-diff versions,
-		// we pass --dry-run to `helm template` only if Helm is greater than v3.13.0.
-		if useDryRunService, err := isHelmVersionAtLeast(minHelmVersionWithDryRunLookupSupport); err == nil && useDryRunService {
-			// However, which dry-run mode to use is still not clear.
-			//
-			// For compatibility with the old and new helm-diff options,
-			// old and new helm, we assume that the user wants to use the older `helm template --dry-run=client` mode
-			// if helm-diff has been invoked with any of the following flags:
-			//
-			// * no dry-run flags (to be consistent with helm-template)
-			// * --dry-run
-			// * --dry-run=""
-			// * --dry-run=client
-			//
-			// and the newer `helm template --dry-run=server` mode when invoked with:
-			//
-			// * --dry-run=server
-			//
-			// Any other values should result in errors.
-			//
-			// See the fllowing link for more details:
-			// - https://github.com/databus23/helm-diff/pull/458
-			// - https://github.com/helm/helm/pull/9426#issuecomment-1501005666
-			if d.dryRunMode == "server" {
-				// This is for security reasons!
-				//
-				// We give helm-template the additional cluster access for the helm `lookup` function
-				// only if the user has explicitly requested it by --dry-run=server,
-				//
-				// In other words, although helm-diff-upgrade implies limited cluster access by default,
-				// helm-diff-upgrade without a --dry-run flag does NOT imply
-				// full cluster-access via helm-template --dry-run=server!
-				flags = append(flags, "--dry-run=server")
-			} else {
-				// Since helm-diff 3.9.0 and helm 3.13.0, we pass --dry-run=client to `helm template` by default.
-				// This doesn't make any difference for helm-diff itself,
-				// because helm-template w/o flags is equivalent to helm-template --dry-run=client.
-				// See https://github.com/helm/helm/pull/9426#discussion_r1181397259
-				flags = append(flags, "--dry-run=client")
-			}
-		}
-
-		subcmd = "template"
-
-		filter = func(s []byte) []byte {
-			return s
-		}
-	}
-
-	args := []string{subcmd, d.release, d.chart}
-	args = append(args, flags...)
-
-	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
-	out, err := outputWithRichError(cmd)
-	return filter(out), err
-}
-
-func (d *diffCmd) writeExistingValues(f *os.File, all bool) error {
-	args := []string{"get", "values", d.release, "--output", "yaml"}
-	if all {
-		args = append(args, "--all")
-	}
-	cmd := exec.Command(os.Getenv("HELM_BIN"), args...)
-	debugPrint("Executing %s", strings.Join(cmd.Args, " "))
-	defer func() {
-		_ = f.Close()
-	}()
-	cmd.Stdout = f
-	return cmd.Run()
-}
-
-func extractManifestFromHelmUpgradeDryRunOutput(s []byte, noHooks bool) []byte {
-	if len(s) == 0 {
-		return s
-	}
-
-	var (
-		hooks    []byte
-		manifest []byte
-	)
-
-	i := bytes.Index(s, []byte("HOOKS:"))
-	if i > -1 {
-		hooks = s[i:]
-	}
-
-	j := bytes.Index(hooks, []byte("MANIFEST:"))
-	if j > -1 {
-		manifest = hooks[j:]
-		hooks = hooks[:j]
-	}
-
-	k := bytes.Index(manifest, []byte("\nNOTES:"))
-
-	if k > -1 {
-		manifest = manifest[:k+1]
-	}
-
-	if noHooks {
-		hooks = nil
-	} else {
-		a := bytes.Index(hooks, []byte("---"))
-		if a > -1 {
-			hooks = hooks[a:]
-		} else {
-			hooks = nil
-		}
-	}
-
-	a := bytes.Index(manifest, []byte("---"))
-	if a > -1 {
-		manifest = manifest[a:]
-	}
-
-	r := []byte{}
-	r = append(r, manifest...)
-	r = append(r, hooks...)
-
-	return r
-}
diff --git a/cmd/upgrade.go b/cmd/upgrade.go
index 48945cf..40d01b4 100644
--- a/cmd/upgrade.go
+++ b/cmd/upgrade.go
@@ -11,9 +11,9 @@ import (
 	"strings"
 
 	"github.com/spf13/cobra"
-	"helm.sh/helm/v3/pkg/action"
-	"helm.sh/helm/v3/pkg/cli"
-	"helm.sh/helm/v3/pkg/kube"
+	"helm.sh/helm/v4/pkg/action"
+	"helm.sh/helm/v4/pkg/cli"
+	"helm.sh/helm/v4/pkg/kube"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/cli-runtime/pkg/resource"
 
@@ -295,7 +295,7 @@ func (d *diffCmd) runHelm3() error {
 	var actionConfig *action.Configuration
 	if d.threeWayMerge || d.takeOwnership {
 		actionConfig = new(action.Configuration)
-		if err := actionConfig.Init(envSettings.RESTClientGetter(), envSettings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil {
+		if err := actionConfig.Init(envSettings.RESTClientGetter(), envSettings.Namespace(), os.Getenv("HELM_DRIVER")); err != nil {
 			log.Fatalf("%+v", err)
 		}
 		if err := actionConfig.KubeClient.IsReachable(); err != nil {
diff --git a/go.mod b/go.mod
index 9e1a116..a35cb58 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/stretchr/testify v1.11.1
 	golang.org/x/term v0.36.0
 	gopkg.in/yaml.v2 v2.4.0
-	helm.sh/helm/v3 v3.19.0
+	helm.sh/helm/v4 v4.0.0
 	k8s.io/api v0.34.1
 	k8s.io/apiextensions-apiserver v0.34.1
 	k8s.io/apimachinery v0.34.1
@@ -34,26 +34,26 @@ require (
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/sprig/v3 v3.3.0 // indirect
 	github.com/Masterminds/squirrel v1.5.4 // indirect
+	github.com/ProtonMail/go-crypto v1.3.0 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/chai2010/gettext-go v1.0.2 // indirect
-	github.com/containerd/containerd v1.7.29 // indirect
-	github.com/containerd/errdefs v0.3.0 // indirect
-	github.com/containerd/log v0.1.0 // indirect
-	github.com/containerd/platforms v0.2.1 // indirect
-	github.com/cyphar/filepath-securejoin v0.4.1 // indirect
+	github.com/cloudflare/circl v1.6.1 // indirect
+	github.com/cyphar/filepath-securejoin v0.5.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
 	github.com/emicklei/go-restful/v3 v3.12.2 // indirect
-	github.com/evanphx/json-patch v5.9.11+incompatible // indirect
 	github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
-	github.com/fatih/color v1.13.0 // indirect
+	github.com/extism/go-sdk v1.7.1 // indirect
+	github.com/fatih/color v1.18.0 // indirect
+	github.com/fluxcd/cli-utils v0.36.0-flux.14 // indirect
 	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
-	github.com/go-errors/errors v1.4.2 // indirect
+	github.com/go-errors/errors v1.5.1 // indirect
 	github.com/go-gorp/gorp/v3 v3.1.0 // indirect
 	github.com/go-logr/logr v1.4.3 // indirect
-	github.com/go-openapi/jsonpointer v0.21.0 // indirect
-	github.com/go-openapi/jsonreference v0.20.2 // indirect
-	github.com/go-openapi/swag v0.23.0 // indirect
+	github.com/go-openapi/jsonpointer v0.21.1 // indirect
+	github.com/go-openapi/jsonreference v0.21.0 // indirect
+	github.com/go-openapi/swag v0.23.1 // indirect
 	github.com/gobwas/glob v0.2.3 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/gonvenience/idem v0.0.2 // indirect
@@ -66,19 +66,17 @@ require (
 	github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
 	github.com/gosuri/uitable v0.0.4 // indirect
 	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
-	github.com/hashicorp/errwrap v1.1.0 // indirect
-	github.com/hashicorp/go-multierror v1.1.1 // indirect
 	github.com/huandu/xstrings v1.5.0 // indirect
+	github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jmoiron/sqlx v1.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
 	github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
 	github.com/lib/pq v1.10.9 // indirect
 	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
-	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mailru/easyjson v0.9.0 // indirect
 	github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
@@ -105,24 +103,24 @@ require (
 	github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
 	github.com/sergi/go-diff v1.4.0 // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
-	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spf13/cast v1.7.0 // indirect
+	github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
+	github.com/tetratelabs/wazero v1.9.0 // indirect
 	github.com/texttheater/golang-levenshtein v1.0.1 // indirect
 	github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
-	golang.org/x/crypto v0.41.0 // indirect
-	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/crypto v0.43.0 // indirect
+	golang.org/x/net v0.45.0 // indirect
 	golang.org/x/oauth2 v0.30.0 // indirect
-	golang.org/x/sync v0.16.0 // indirect
+	golang.org/x/sync v0.17.0 // indirect
 	golang.org/x/sys v0.37.0 // indirect
-	golang.org/x/text v0.28.0 // indirect
+	golang.org/x/text v0.30.0 // indirect
 	golang.org/x/time v0.12.0 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/grpc v1.72.1 // indirect
-	google.golang.org/protobuf v1.36.5 // indirect
+	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -130,9 +128,10 @@ require (
 	k8s.io/component-base v0.34.1 // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
-	k8s.io/kubectl v0.34.0 // indirect
+	k8s.io/kubectl v0.34.1 // indirect
 	k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
 	oras.land/oras-go/v2 v2.6.0 // indirect
+	sigs.k8s.io/controller-runtime v0.22.3 // indirect
 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
 	sigs.k8s.io/kustomize/api v0.20.1 // indirect
 	sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
diff --git a/go.sum b/go.sum
index ca2e1cd..b1dfc2f 100644
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,8 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
 github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
 github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
+github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
+github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
@@ -38,22 +40,15 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
 github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
-github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE=
-github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs=
-github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
-github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
-github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
-github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
-github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
+github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
+github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
 github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
-github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
+github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
+github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -72,40 +67,44 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
 github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
 github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
+github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
+github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
 github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
 github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
-github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
-github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
 github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
 github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
 github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
-github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
+github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA=
+github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fluxcd/cli-utils v0.36.0-flux.14 h1:I//AMVUXTc+M04UtIXArMXQZCazGMwfemodV1j/yG8c=
+github.com/fluxcd/cli-utils v0.36.0-flux.14/go.mod h1:uDo7BYOfbdmk/asnHuI0IQPl6u0FCgcN54AHDu3Y5As=
 github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
 github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
 github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
-github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
-github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
+github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
+github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
 github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
 github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
 github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
-github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
-github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
-github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
-github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
-github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
-github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
+github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
+github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
+github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
 github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -114,8 +113,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/gonvenience/bunt v1.4.2 h1:nTgkFZsw38SIJKABhLj8aXj2rqion9Zo1so/EBkbFBY=
 github.com/gonvenience/bunt v1.4.2/go.mod h1:WjyEO2rSYR+OLZg67Ucl+gjdXPs8GpFl63SCA02XDyI=
 github.com/gonvenience/idem v0.0.2 h1:jWHknjPfSbiWgYKre9wB2FhMgVLd1RWXCXzVq+7VIWg=
@@ -135,8 +132,8 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
-github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
+github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
@@ -151,11 +148,6 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr
 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=
 github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
 github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
@@ -164,6 +156,8 @@ github.com/homeport/dyff v1.10.2 h1:XyB+D0KVwjbUFTZYIkvPtsImwkfh+ObH2CEdEHTqdr4=
 github.com/homeport/dyff v1.10.2/go.mod h1:0kIjL/JOGaXigzrLY6kcl5esSStbAa99r6GzEvr7lrs=
 github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
 github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
+github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
@@ -177,7 +171,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -194,15 +187,12 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
+github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
 github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk=
 github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@@ -250,8 +240,6 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
 github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
 github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
-github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -261,12 +249,12 @@ github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
 github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
 github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
 github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
-github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
-github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
 github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho=
 github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=
 github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
@@ -295,8 +283,6 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
 github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
 github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -304,11 +290,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
+github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
+github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
+github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
 github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U=
 github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8=
 github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
@@ -327,8 +314,8 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQ
 go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
-go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
-go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
+go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
@@ -353,22 +340,26 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu
 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
 go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
 go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
-go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
-go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
+go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
 go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
 go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
 go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
 go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
 go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
 go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
-go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
-go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
+go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
 go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
 go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
 go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
 go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
 go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
 go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@@ -376,33 +367,29 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
-golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
-golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
+golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
+golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
-golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
+golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
 golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
-golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
+golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
@@ -411,29 +398,28 @@ golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
 golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
-golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
-golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
+golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
+golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
 google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
 google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
-google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
-google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -448,8 +434,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k=
-helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc=
+helm.sh/helm/v4 v4.0.0 h1:Ppai7cygdmyxSR+JR9djUoVrRmyMI/yY5P5TBd25oHs=
+helm.sh/helm/v4 v4.0.0/go.mod h1:G1Y5AE+lJPQSAjh7nbXnhZrtGtxo+I6POSu9DruYiGI=
 k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
 k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
 k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
@@ -468,12 +454,14 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
-k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs=
-k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4=
+k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI=
+k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A=
 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
 oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
+sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y=
+sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
 sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
diff --git a/manifest/generate.go b/manifest/generate.go
index f59bf10..23b8f6b 100644
--- a/manifest/generate.go
+++ b/manifest/generate.go
@@ -7,8 +7,8 @@ import (
 
 	jsonpatch "github.com/evanphx/json-patch/v5"
 	jsoniter "github.com/json-iterator/go"
-	"helm.sh/helm/v3/pkg/action"
-	"helm.sh/helm/v3/pkg/kube"
+	"helm.sh/helm/v4/pkg/action"
+	"helm.sh/helm/v4/pkg/kube"
 	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
diff --git a/plugin.yaml b/plugin.yaml
index 451a468..8ebfb8c 100644
--- a/plugin.yaml
+++ b/plugin.yaml
@@ -16,8 +16,8 @@ platformHooks:
     - os: windows
       command: pwsh
       args:
-          - -c
-          - ${HELM_PLUGIN_DIR}/install-binary.ps1
+        - -c
+        - ${HELM_PLUGIN_DIR}\install-binary.ps1
   update:
     - command: ${HELM_PLUGIN_DIR}/install-binary.sh
       args:
@@ -26,5 +26,5 @@ platformHooks:
       command: pwsh
       args:
         - -c
-        - ${HELM_PLUGIN_DIR}/install-binary.ps1
-        - -Update
\ No newline at end of file
+        - ${HELM_PLUGIN_DIR}\install-binary.ps1
+        - -Update
diff --git a/scripts/verify-staticcheck.sh b/scripts/verify-staticcheck.sh
deleted file mode 100755
index b061a3c..0000000
--- a/scripts/verify-staticcheck.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-# script credits : https://github.com/infracloudio/botkube
-
-set -o nounset
-set -o pipefail
-
-find_packages() {
-  find . -not \( \
-      \( \
-        -wholename '*/vendor/*' \
-      \) -prune \
-    \) -name '*.go' -exec dirname '{}' ';' | sort -u
-}
-
-errors="$(find_packages | xargs -I@ bash -c "staticcheck @")"
-if [[ -n "${errors}" ]]; then
-  echo "${errors}"
-  exit 1
-fi



Test patch:
diff --git a/cmd/helm3_test.go b/cmd/helm_test.go
similarity index 100%
rename from cmd/helm3_test.go
rename to cmd/helm_test.go