Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package nelm for openSUSE:Factory checked in 
at 2026-09-21 12:07:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/nelm (Old)
 and      /work/SRC/openSUSE:Factory/.nelm.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "nelm"

Mon Sep 21 12:07:56 2026 rev:33 rq:1379169 version:1.26.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/nelm/nelm.changes        2026-07-21 
23:11:52.609326299 +0200
+++ /work/SRC/openSUSE:Factory/.nelm.new.383539/nelm.changes    2026-09-21 
12:08:00.996165091 +0200
@@ -1,0 +2,19 @@
+Sun Sep 20 05:22:42 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.26.2:
+  * Bug Fixes
+    - fix: `werf.io/deploy-dependency` didn't work if no readiness
+      operation in plan or if `namespace=releasens`
+- Update to version 1.26.1:
+  * Bug Fixes
+    - fix: `werf.io/resource-policy` should only respect
+      skip-delete from live
+
+-------------------------------------------------------------------
+Tue Aug 11 11:22:27 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.26.0:
+  * Features
+    - add werf.io/resource-policy annotation (5a2517f)
+
+-------------------------------------------------------------------

Old:
----
  nelm-1.25.3.obscpio

New:
----
  nelm-1.26.2.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ nelm.spec ++++++
--- /var/tmp/diff_new_pack.YknDgy/_old  2026-09-21 12:08:02.976248104 +0200
+++ /var/tmp/diff_new_pack.YknDgy/_new  2026-09-21 12:08:02.978248188 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           nelm
-Version:        1.25.3
+Version:        1.26.2
 Release:        0
 Summary:        Helm 3 alternative
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.YknDgy/_old  2026-09-21 12:08:03.019249907 +0200
+++ /var/tmp/diff_new_pack.YknDgy/_new  2026-09-21 12:08:03.022250032 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/werf/nelm.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v1.25.3</param>
+    <param name="revision">refs/tags/v1.26.2</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.YknDgy/_old  2026-09-21 12:08:03.044250955 +0200
+++ /var/tmp/diff_new_pack.YknDgy/_new  2026-09-21 12:08:03.048251122 +0200
@@ -3,6 +3,6 @@
                 <param name="url">https://github.com/werf/nelm</param>
               <param 
name="changesrevision">9366af9135202b7d705c71fef6c7dbe0231c0345</param></service><service
 name="tar_scm">
                 <param name="url">https://github.com/werf/nelm.git</param>
-              <param 
name="changesrevision">5d3d03cb0b97bdb6df7a5b22320bfbf53edafdb4</param></service></servicedata>
+              <param 
name="changesrevision">6505774f6217ec78612d8ca6ccad1e5b3e2cea7f</param></service></servicedata>
 (No newline at EOF)
 

++++++ nelm-1.25.3.obscpio -> nelm-1.26.2.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/.pp/.gitignore 
new/nelm-1.26.2/.pp/.gitignore
--- old/nelm-1.25.3/.pp/.gitignore      2026-06-24 21:01:58.000000000 +0200
+++ new/nelm-1.26.2/.pp/.gitignore      2026-07-16 10:38:47.000000000 +0200
@@ -1,2 +1,3 @@
 state/
 config.json
+logs/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/CHANGELOG.md new/nelm-1.26.2/CHANGELOG.md
--- old/nelm-1.25.3/CHANGELOG.md        2026-06-24 21:01:58.000000000 +0200
+++ new/nelm-1.26.2/CHANGELOG.md        2026-07-16 10:38:47.000000000 +0200
@@ -1,5 +1,33 @@
 # Changelog
 
+## [1.26.2](https://github.com/werf/nelm/compare/v1.26.1...v1.26.2) 
(2026-07-16)
+
+
+### Bug Fixes
+
+* `werf.io/deploy-dependency` didn't work if no readiness operation in plan or 
if `namespace=releasens` 
([4c9dd90](https://github.com/werf/nelm/commit/4c9dd90629b9e49563f74773c6df41ae504e23c6))
+
+## [1.26.1](https://github.com/werf/nelm/compare/v1.26.0...v1.26.1) 
(2026-07-06)
+
+
+### Bug Fixes
+
+* `werf.io/resource-policy` should only respect skip-delete from live 
([ddfb620](https://github.com/werf/nelm/commit/ddfb620f23e2caae4c27c44bb85ebd4f82f0c4db))
+
+## [1.26.0](https://github.com/werf/nelm/compare/v1.25.3...v1.26.0) 
(2026-07-03)
+
+
+### Features
+
+* add `werf.io/resource-policy` annotation 
([5a2517f](https://github.com/werf/nelm/commit/5a2517f7c0817716f0686de8fa26e97f23c95609))
+
+## [1.25.3](https://github.com/werf/nelm/compare/v1.25.2...v1.25.3) 
(2026-06-24)
+
+
+### Bug Fixes
+
+* HelmCompatibleTracking mode should allow annotations override 
([6c7e6f4](https://github.com/werf/nelm/commit/6c7e6f429c37126160f0bb657904966ef4bef618))
+
 ## [1.25.3](https://github.com/werf/nelm/compare/v1.25.2...v1.25.3) 
(2026-06-24)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/common/common.go 
new/nelm-1.26.2/pkg/common/common.go
--- old/nelm-1.25.3/pkg/common/common.go        2026-06-24 21:01:58.000000000 
+0200
+++ new/nelm-1.26.2/pkg/common/common.go        2026-07-16 10:38:47.000000000 
+0200
@@ -56,6 +56,17 @@
        // The resource is owned by a single release.
        OwnershipRelease Ownership = "release"
 
+       // Skip creating the resource if it does not exist in the cluster.
+       ResourcePolicySkipCreate ResourcePolicy = "skip-create"
+       // Skip updating the resource if it already exists in the cluster.
+       ResourcePolicySkipUpdate ResourcePolicy = "skip-update"
+       // Skip recreating the resource when a recreation would otherwise be 
required.
+       ResourcePolicySkipRecreate ResourcePolicy = "skip-recreate"
+       // Skip deleting the resource (protect it from deletion). Alias: keep.
+       ResourcePolicySkipDelete ResourcePolicy = "skip-delete"
+       // Alias for skipDelete, matching the legacy helm.sh/resource-policy 
value.
+       ResourcePolicyKeep ResourcePolicy = "keep"
+
        ResourceStateAbsent  ResourceState = "absent"
        ResourceStatePresent ResourceState = "present"
        ResourceStateReady   ResourceState = "ready"
@@ -165,6 +176,8 @@
        AnnotationKeyPatternHook                            = 
regexp.MustCompile(`^helm.sh/hook$`)
        AnnotationKeyHumanResourcePolicy                    = 
"helm.sh/resource-policy"
        AnnotationKeyPatternResourcePolicy                  = 
regexp.MustCompile(`^helm.sh/resource-policy$`)
+       AnnotationKeyHumanWerfResourcePolicy                = 
"werf.io/resource-policy"
+       AnnotationKeyPatternWerfResourcePolicy              = 
regexp.MustCompile(`^werf.io/resource-policy$`)
        AnnotationKeyHumanDeletePolicy                      = 
"werf.io/delete-policy"
        AnnotationKeyPatternDeletePolicy                    = 
regexp.MustCompile(`^werf.io/delete-policy$`)
        AnnotationKeyHumanHookDeletePolicy                  = 
"helm.sh/hook-delete-policy"
@@ -242,6 +255,9 @@
 // Configures resource deletions during deployment of this resource.
 type DeletePolicy string
 
+// Configures which lifecycle operations are allowed for this resource.
+type ResourcePolicy string
+
 // Resource ownership.
 type Ownership string
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/plan/export_test.go 
new/nelm-1.26.2/pkg/plan/export_test.go
--- old/nelm-1.25.3/pkg/plan/export_test.go     2026-06-24 21:01:58.000000000 
+0200
+++ new/nelm-1.26.2/pkg/plan/export_test.go     2026-07-16 10:38:47.000000000 
+0200
@@ -1,6 +1,7 @@
 package plan
 
 var (
-       BuildInstallableResourceInfo = buildInstallableResourceInfo
-       BuildDeletableResourceInfo   = buildDeletableResourceInfo
+       BuildInstallableResourceInfo                    = 
buildInstallableResourceInfo
+       BuildDeletableResourceInfo                      = 
buildDeletableResourceInfo
+       ForceReadinessTrackingForReadyDependencyTargets = 
forceReadinessTrackingForReadyDependencyTargets
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/plan/plan_build.go 
new/nelm-1.26.2/pkg/plan/plan_build.go
--- old/nelm-1.25.3/pkg/plan/plan_build.go      2026-06-24 21:01:58.000000000 
+0200
+++ new/nelm-1.26.2/pkg/plan/plan_build.go      2026-07-16 10:38:47.000000000 
+0200
@@ -469,7 +469,7 @@
                                Iteration: OperationIteration(info.Iteration),
                                Config: &OperationConfigTrackReadiness{
                                        ResourceMeta:                           
  info.ResourceMeta,
-                                       FailMode:                               
  info.LocalResource.FailMode,
+                                       FailMode:                               
  info.FailMode,
                                        FailuresAllowed:                        
  info.LocalResource.FailuresAllowed,
                                        IgnoreLogs:                             
  info.LocalResource.SkipLogs,
                                        IgnoreLogsForContainers:                
  info.LocalResource.SkipLogsForContainers,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/nelm-1.25.3/pkg/plan/ready_dependency_override_ai_test.go 
new/nelm-1.26.2/pkg/plan/ready_dependency_override_ai_test.go
--- old/nelm-1.25.3/pkg/plan/ready_dependency_override_ai_test.go       
1970-01-01 01:00:00.000000000 +0100
+++ new/nelm-1.26.2/pkg/plan/ready_dependency_override_ai_test.go       
2026-07-16 10:38:47.000000000 +0200
@@ -0,0 +1,397 @@
+//go:build ai_tests
+
+package plan_test
+
+import (
+       "context"
+       "testing"
+       "time"
+
+       "github.com/samber/lo"
+       "github.com/stretchr/testify/require"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+
+       "github.com/werf/kubedog/pkg/trackers/rollout/multitrack"
+       "github.com/werf/nelm/pkg/common"
+       "github.com/werf/nelm/pkg/kube"
+       "github.com/werf/nelm/pkg/kube/fake"
+       "github.com/werf/nelm/pkg/plan"
+       "github.com/werf/nelm/pkg/resource"
+       "github.com/werf/nelm/pkg/resource/spec"
+)
+
+const (
+       readyDepReleaseName      = "test-release"
+       readyDepReleaseNamespace = "test-namespace"
+)
+
+func TestAI_ReadyDependencyCrossStageDoesNotForceTracking(t *testing.T) {
+       target := readyDepInstallableResource(
+               readyDepConfigMapSpec("target", readyDepReleaseNamespace, nil),
+               multitrack.NonBlocking,
+               multitrack.IgnoreAndContinueDeployProcess,
+       )
+
+       dependent := readyDepInstallableResource(
+               readyDepConfigMapSpec("dependent", readyDepReleaseNamespace, 
nil),
+               multitrack.WaitUntilResourceReady,
+               multitrack.FailWholeDeployProcessImmediately,
+       )
+       dependent.DeployConditions = map[common.On][]common.Stage{
+               common.InstallOnInstall:  {common.StagePostInstall},
+               common.InstallOnUpgrade:  {common.StagePostInstall},
+               common.InstallOnRollback: {common.StagePostInstall},
+       }
+       dependent.ManualInternalDependencies = []*resource.InternalDependency{
+               {
+                       ResourceMatcher: &spec.ResourceMatcher{
+                               Names:  []string{"target"},
+                               Groups: []string{""},
+                               Kinds:  []string{"ConfigMap"},
+                       },
+                       ResourceState: common.ResourceStateReady,
+               },
+       }
+
+       infos := buildReadyDepInfos(t, target, dependent, nil)
+
+       targetInfo := findInfo(t, infos, "target")
+       require.False(t, targetInfo.MustTrackReadiness,
+               "a cross-stage state=ready dep must not force tracking, since 
no ordering edge can form")
+       require.Equal(t, multitrack.IgnoreAndContinueDeployProcess, 
targetInfo.FailMode,
+               "fail mode must remain the resource's own value when not 
forced")
+}
+
+func TestAI_ReadyDependencyDoesNotForceCRDTarget(t *testing.T) {
+       crdInfo := &plan.InstallableResourceInfo{
+               ResourceMeta: &spec.ResourceMeta{
+                       Name:             "widgets.example.com",
+                       GroupVersionKind: schema.GroupVersionKind{Group: 
"apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"},
+               },
+               LocalResource: &resource.InstallableResource{},
+               MustInstall:   plan.ResourceInstallTypeCreate,
+               Stage:         common.StagePrePreInstall,
+       }
+
+       // A Namespace also deploys in pre-pre-install, so it shares a stage 
with a CRD and can carry
+       // a ready dependency targeting one; the CRD must still be excluded 
from forced tracking.
+       dependentInfo := &plan.InstallableResourceInfo{
+               ResourceMeta: &spec.ResourceMeta{
+                       Name:             "my-namespace",
+                       GroupVersionKind: schema.GroupVersionKind{Version: 
"v1", Kind: "Namespace"},
+               },
+               LocalResource: &resource.InstallableResource{
+                       ManualInternalDependencies: 
[]*resource.InternalDependency{
+                               {
+                                       ResourceMatcher: &spec.ResourceMatcher{
+                                               Names:  
[]string{"widgets.example.com"},
+                                               Groups: 
[]string{"apiextensions.k8s.io"},
+                                               Kinds:  
[]string{"CustomResourceDefinition"},
+                                       },
+                                       ResourceState: 
common.ResourceStateReady,
+                               },
+                       },
+               },
+               Stage: common.StagePrePreInstall,
+       }
+
+       
plan.ForceReadinessTrackingForReadyDependencyTargets([]*plan.InstallableResourceInfo{crdInfo,
 dependentInfo})
+
+       require.False(t, crdInfo.MustTrackReadiness,
+               "a CRD target must never be forced to track even when a 
same-stage dependent selects it")
+       require.NotEqual(t, multitrack.FailWholeDeployProcessImmediately, 
crdInfo.FailMode)
+}
+
+func TestAI_ReadyDependencyDoesNotForceSkipCreateAbsentTarget(t *testing.T) {
+       target := readyDepInstallableResource(
+               readyDepConfigMapSpec("target", readyDepReleaseNamespace, nil),
+               multitrack.NonBlocking,
+               multitrack.IgnoreAndContinueDeployProcess,
+       )
+       target.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipCreate}
+       dependent := readyDependentResource("target", "")
+
+       infos := buildReadyDepInfos(t, target, dependent, nil)
+
+       targetInfo := findInfo(t, infos, "target")
+       require.Equal(t, plan.ResourceInstallTypeNone, targetInfo.MustInstall)
+       require.Nil(t, targetInfo.GetResult, "target is absent")
+       require.False(t, targetInfo.MustTrackReadiness,
+               "an absent skip-create target that will never be created must 
not be force-tracked")
+       require.Equal(t, multitrack.IgnoreAndContinueDeployProcess, 
targetInfo.FailMode,
+               "fail mode must remain the resource's own value when not 
forced")
+}
+
+func TestAI_ReadyDependencyDoesNotForceUnmatchedTarget(t *testing.T) {
+       target := readyDepInstallableResource(
+               readyDepConfigMapSpec("target", readyDepReleaseNamespace, nil),
+               multitrack.NonBlocking,
+               multitrack.IgnoreAndContinueDeployProcess,
+       )
+       dependent := readyDependentResource("some-other-name", "")
+
+       infos := buildReadyDepInfos(t, target, dependent, nil)
+
+       targetInfo := findInfo(t, infos, "target")
+       require.False(t, targetInfo.MustTrackReadiness, "NonBlocking target not 
selected by any dep stays untracked")
+       require.Equal(t, multitrack.IgnoreAndContinueDeployProcess, 
targetInfo.FailMode,
+               "fail mode must remain the resource's own value when not 
forced")
+}
+
+func TestAI_ReadyDependencyForcesTrackingOnChartAuthoredNonBlockingTarget(t 
*testing.T) {
+       target := readyDepInstallableResource(
+               readyDepConfigMapSpec("target", readyDepReleaseNamespace, 
map[string]string{
+                       "werf.io/track-termination-mode": "NonBlocking",
+                       "werf.io/fail-mode":              
"IgnoreAndContinueDeployProcess",
+               }),
+               multitrack.NonBlocking,
+               multitrack.IgnoreAndContinueDeployProcess,
+       )
+       dependent := readyDependentResource("target", "")
+
+       infos := buildReadyDepInfos(t, target, dependent, nil)
+
+       targetInfo := findInfo(t, infos, "target")
+       require.True(t, targetInfo.MustTrackReadiness)
+       require.Equal(t, multitrack.FailWholeDeployProcessImmediately, 
targetInfo.FailMode)
+}
+
+func TestAI_ReadyDependencyForcesTrackingOnLegacyPatchedTarget(t *testing.T) {
+       target := readyDepInstallableResource(
+               readyDepConfigMapSpec("target", readyDepReleaseNamespace, nil),
+               multitrack.NonBlocking,
+               multitrack.IgnoreAndContinueDeployProcess,
+       )
+       dependent := readyDependentResource("target", "")
+
+       infos := buildReadyDepInfos(t, target, dependent, nil)
+
+       targetInfo := findInfo(t, infos, "target")
+       require.True(t, targetInfo.MustTrackReadiness, "NonBlocking target 
selected by state=ready dep must be tracked")
+       require.Equal(t, multitrack.FailWholeDeployProcessImmediately, 
targetInfo.FailMode)
+}
+
+func TestAI_ReadyDependencyForcesTrackingOnUnchangedTarget(t *testing.T) {
+       targetSpec := readyDepConfigMapSpec("target", readyDepReleaseNamespace, 
nil)
+       target := readyDepInstallableResource(targetSpec, 
multitrack.NonBlocking, multitrack.IgnoreAndContinueDeployProcess)
+       dependent := readyDependentResource("target", "")
+
+       infos := buildReadyDepInfos(t, target, dependent, func(cf 
*fake.ClientFactory) {
+               _, err := cf.KubeClient().Create(context.Background(), 
targetSpec, kube.KubeClientCreateOptions{
+                       DefaultNamespace: readyDepReleaseNamespace,
+               })
+               require.NoError(t, err)
+       })
+
+       targetInfo := findInfo(t, infos, "target")
+       require.Equal(t, plan.ResourceInstallTypeNone, targetInfo.MustInstall, 
"target must be an unchanged no-op")
+       require.True(t, targetInfo.MustTrackReadiness, "unchanged target still 
forced to track when selected by state=ready dep")
+       require.Equal(t, multitrack.FailWholeDeployProcessImmediately, 
targetInfo.FailMode)
+}
+
+func TestAI_ReadyDependencyProducesEdgeAndRetainsTrackingWithNoFinalTracking(t 
*testing.T) {
+       target := readyDepInstallableResource(
+               readyDepConfigMapSpec("target", readyDepReleaseNamespace, nil),
+               multitrack.NonBlocking,
+               multitrack.IgnoreAndContinueDeployProcess,
+       )
+       dependent := readyDependentResource("target", "")
+
+       infos := buildReadyDepInfos(t, target, dependent, nil)
+
+       p, err := plan.BuildPlan(infos, nil, nil, 
plan.BuildPlanOptions{NoFinalTracking: true})
+       require.NoError(t, err)
+
+       trackOp := findTrackReadinessOp(t, p, "target")
+       createDependentOp := findCreateOp(t, p, "dependent")
+       createTargetOp := findCreateOp(t, p, "target")
+
+       require.True(t, planHasEdge(t, p, createTargetOp.ID(), trackOp.ID()),
+               "target create must precede its track-readiness op")
+       require.True(t, planHasEdge(t, p, trackOp.ID(), createDependentOp.ID()),
+               "target track-readiness must precede dependent create 
(ready-dependency edge)")
+
+       cfg := trackOp.Config.(*plan.OperationConfigTrackReadiness)
+       require.Equal(t, multitrack.FailWholeDeployProcessImmediately, 
cfg.FailMode,
+               "forced readiness op must fail the whole deploy despite 
IgnoreAndContinue annotation")
+}
+
+func TestAI_ReadyDependencyReleaseNamespaceSelectorProducesEdge(t *testing.T) {
+       cf, err := fake.NewClientFactory(context.Background())
+       require.NoError(t, err)
+
+       targetSpec := readyDepConfigMapSpec("target", readyDepReleaseNamespace, 
nil)
+       target, err := resource.NewInstallableResource(targetSpec, nil, 
readyDepReleaseNamespace, cf, resource.InstallableResourceOptions{})
+       require.NoError(t, err)
+
+       dependentSpec := readyDepConfigMapSpec("dependent", 
readyDepReleaseNamespace, map[string]string{
+               "werf.io/deploy-dependency-target": 
"state=ready,version=v1,kind=ConfigMap,name=target,namespace=" + 
readyDepReleaseNamespace,
+       })
+       dependent, err := resource.NewInstallableResource(dependentSpec, nil, 
readyDepReleaseNamespace, cf, resource.InstallableResourceOptions{})
+       require.NoError(t, err)
+
+       instInfos, _, err := plan.BuildResourceInfos(
+               context.Background(),
+               common.DeployTypeInitial,
+               readyDepReleaseName,
+               readyDepReleaseNamespace,
+               []*resource.InstallableResource{target, dependent},
+               nil,
+               false,
+               cf,
+               plan.BuildResourceInfosOptions{NetworkParallelism: 10},
+       )
+       require.NoError(t, err)
+
+       p, err := plan.BuildPlan(instInfos, nil, nil, 
plan.BuildPlanOptions{NoFinalTracking: true})
+       require.NoError(t, err)
+
+       trackOp := findTrackReadinessOp(t, p, "target")
+       createDependentOp := findCreateOp(t, p, "dependent")
+
+       require.True(t, planHasEdge(t, p, trackOp.ID(), createDependentOp.ID()),
+               "a namespace=<release-ns> selector must normalize to the 
target's empty namespace and produce the ready-dependency edge")
+}
+
+func readyDependentResource(targetName, targetNamespace string) 
*resource.InstallableResource {
+       res := readyDepInstallableResource(
+               readyDepConfigMapSpec("dependent", readyDepReleaseNamespace, 
nil),
+               multitrack.WaitUntilResourceReady,
+               multitrack.FailWholeDeployProcessImmediately,
+       )
+       res.ManualInternalDependencies = []*resource.InternalDependency{
+               {
+                       ResourceMatcher: &spec.ResourceMatcher{
+                               Names:      []string{targetName},
+                               Namespaces: []string{targetNamespace},
+                               Groups:     []string{""},
+                               Kinds:      []string{"ConfigMap"},
+                       },
+                       ResourceState: common.ResourceStateReady,
+               },
+       }
+
+       return res
+}
+
+func buildReadyDepInfos(t *testing.T, target, dependent 
*resource.InstallableResource, prepare func(cf *fake.ClientFactory)) 
[]*plan.InstallableResourceInfo {
+       t.Helper()
+
+       cf, err := fake.NewClientFactory(context.Background())
+       require.NoError(t, err)
+
+       if prepare != nil {
+               prepare(cf)
+       }
+
+       instInfos, _, err := plan.BuildResourceInfos(
+               context.Background(),
+               common.DeployTypeInitial,
+               readyDepReleaseName,
+               readyDepReleaseNamespace,
+               []*resource.InstallableResource{target, dependent},
+               nil,
+               false,
+               cf,
+               plan.BuildResourceInfosOptions{NetworkParallelism: 10},
+       )
+       require.NoError(t, err)
+
+       return instInfos
+}
+
+func findCreateOp(t *testing.T, p *plan.Plan, name string) *plan.Operation {
+       t.Helper()
+
+       op, found := lo.Find(p.Operations(), func(op *plan.Operation) bool {
+               cfg, ok := op.Config.(*plan.OperationConfigCreate)
+
+               return ok && cfg.ResourceSpec.Name == name
+       })
+       require.Truef(t, found, "create op for %q not found", name)
+
+       return op
+}
+
+func findInfo(t *testing.T, infos []*plan.InstallableResourceInfo, name 
string) *plan.InstallableResourceInfo {
+       t.Helper()
+
+       info, found := lo.Find(infos, func(i *plan.InstallableResourceInfo) 
bool {
+               return i.Name == name
+       })
+       require.Truef(t, found, "info for %q not found", name)
+
+       return info
+}
+
+func findTrackReadinessOp(t *testing.T, p *plan.Plan, name string) 
*plan.Operation {
+       t.Helper()
+
+       op, found := lo.Find(p.Operations(), func(op *plan.Operation) bool {
+               cfg, ok := op.Config.(*plan.OperationConfigTrackReadiness)
+
+               return ok && cfg.ResourceMeta.Name == name
+       })
+       require.Truef(t, found, "track-readiness op for %q not found", name)
+
+       return op
+}
+
+func planHasEdge(t *testing.T, p *plan.Plan, fromID, toID string) bool {
+       t.Helper()
+
+       adjMap, err := p.Graph.AdjacencyMap()
+       require.NoError(t, err)
+
+       _, ok := adjMap[fromID][toID]
+
+       return ok
+}
+
+func readyDepConfigMapSpec(name, namespace string, annotations 
map[string]string) *spec.ResourceSpec {
+       meta := map[string]interface{}{
+               "name":      name,
+               "namespace": namespace,
+               "annotations": map[string]interface{}{
+                       "meta.helm.sh/release-name":      readyDepReleaseName,
+                       "meta.helm.sh/release-namespace": 
readyDepReleaseNamespace,
+               },
+               "labels": map[string]interface{}{
+                       "app.kubernetes.io/managed-by": "Helm",
+               },
+       }
+       anns := meta["annotations"].(map[string]interface{})
+       for k, v := range annotations {
+               anns[k] = v
+       }
+
+       resSpec := spec.NewResourceSpec(&unstructured.Unstructured{
+               Object: map[string]interface{}{
+                       "apiVersion": "v1",
+                       "kind":       "ConfigMap",
+                       "metadata":   meta,
+                       "data":       map[string]interface{}{"key": "value"},
+               },
+       }, namespace, spec.ResourceSpecOptions{})
+       resSpec.Unstruct.SetNamespace(namespace)
+
+       return resSpec
+}
+
+func readyDepInstallableResource(resSpec *spec.ResourceSpec, trackTermination 
multitrack.TrackTerminationMode, failMode multitrack.FailMode) 
*resource.InstallableResource {
+       return &resource.InstallableResource{
+               ResourceSpec:                    resSpec,
+               Ownership:                       common.OwnershipRelease,
+               FailMode:                        failMode,
+               NoActivityTimeout:               4 * time.Minute,
+               ShowLogsOnlyForNumberOfReplicas: 1,
+               TrackTerminationMode:            trackTermination,
+               Weight:                          lo.ToPtr(0),
+               DeployConditions: map[common.On][]common.Stage{
+                       common.InstallOnInstall:  {common.StageInstall},
+                       common.InstallOnUpgrade:  {common.StageInstall},
+                       common.InstallOnRollback: {common.StageInstall},
+               },
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/plan/resource_info.go 
new/nelm-1.26.2/pkg/plan/resource_info.go
--- old/nelm-1.25.3/pkg/plan/resource_info.go   2026-06-24 21:01:58.000000000 
+0200
+++ new/nelm-1.26.2/pkg/plan/resource_info.go   2026-07-16 10:38:47.000000000 
+0200
@@ -57,6 +57,7 @@
        MustDeleteOnSuccessfulInstall bool                
`json:"mustDeleteOnSuccessfulInstall"`
        MustDeleteOnFailedInstall     bool                
`json:"mustDeleteOnFailedInstall"`
        MustTrackReadiness            bool                
`json:"mustTrackReadiness"`
+       FailMode                      multitrack.FailMode `json:"failMode"`
 
        Stage                          common.Stage `json:"stage"`
        StageDeleteOnSuccessfulInstall common.Stage 
`json:"stageDeleteOnSuccessfulInstall,omitempty"`
@@ -127,6 +128,8 @@
                return nil, nil, fmt.Errorf("wait for prev release resource 
pool: %w", err)
        }
 
+       forceReadinessTrackingForReadyDependencyTargets(instResourceInfos)
+
        sort.SliceStable(instResourceInfos, func(i, j int) bool {
                return 
InstallableResourceInfoSortByStageHandler(instResourceInfos[i], 
instResourceInfos[j])
        })
@@ -177,57 +180,50 @@
                DefaultNamespace: releaseNamespace,
                TryCache:         true,
        })
-       if getErr != nil {
-               if kube.IsNotFoundErr(getErr) || kube.IsNoSuchKindErr(getErr) {
-                       mustDeleteOnSuccess := 
mustDeleteOnSuccessfulDeploy(localRes, nil, ResourceInstallTypeCreate, 
releaseNamespace)
-                       trackReadiness := mustTrackReadiness(localRes, 
ResourceInstallTypeCreate, false, prevRelFailed, mustDeleteOnSuccess)
-
-                       return lo.Map(stages, func(stg common.Stage, _ int) 
*InstallableResourceInfo {
-                               return &InstallableResourceInfo{
-                                       ResourceMeta:                   
localRes.ResourceMeta,
-                                       LocalResource:                  
localRes,
-                                       MustDeleteOnFailedInstall:      
mustDeleteOnFailedDeploy(localRes, nil, ResourceInstallTypeCreate, 
releaseNamespace, trackReadiness),
-                                       MustDeleteOnSuccessfulInstall:  
mustDeleteOnSuccess,
-                                       MustInstall:                    
ResourceInstallTypeCreate,
-                                       MustTrackReadiness:             
trackReadiness,
-                                       Stage:                          stg,
-                                       StageDeleteOnSuccessfulInstall: 
stageDeleteOnSuccessfulInstall(mustDeleteOnSuccess, stg),
-                               }
-                       }), nil
-               } else {
-                       return nil, fmt.Errorf("get resource %q: %w", 
localRes.IDHuman(), getErr)
-               }
+       if getErr != nil && !kube.IsNotFoundErr(getErr) && 
!kube.IsNoSuchKindErr(getErr) {
+               return nil, fmt.Errorf("get resource %q: %w", 
localRes.IDHuman(), getErr)
        }
 
-       var err error
-
-       getObj, err = fixManagedFieldsInCluster(ctx, releaseNamespace, getObj, 
localRes, noRemoveManualChanges, clientFactory, lastDeployedOrLastRelResSpecs)
-       if err != nil {
-               return nil, fmt.Errorf("fix managed fields for resource %q: 
%w", localRes.IDHuman(), err)
+       var (
+               getMeta          *spec.ResourceMeta
+               dryApplyObj      *unstructured.Unstructured
+               dryApplyErr      error
+               resourcePolicies = localRes.ResourcePolicies
+       )
+       if getErr == nil {
+               var err error
+
+               getObj, err = fixManagedFieldsInCluster(ctx, releaseNamespace, 
getObj, localRes, noRemoveManualChanges, clientFactory, 
lastDeployedOrLastRelResSpecs)
+               if err != nil {
+                       return nil, fmt.Errorf("fix managed fields for resource 
%q: %w", localRes.IDHuman(), err)
+               }
+
+               getMeta = spec.NewResourceMetaFromUnstructured(getObj, 
releaseNamespace, localRes.FilePath)
+               resourcePolicies = resource.ResolveResourcePolicies(localRes, 
getMeta, releaseNamespace)
+
+               dryApplyObj, dryApplyErr = 
clientFactory.KubeClient().Apply(ctx, localRes.ResourceSpec, 
kube.KubeClientApplyOptions{
+                       DefaultNamespace: releaseNamespace,
+                       DryRun:           true,
+               })
        }
 
-       dryApplyObj, dryApplyErr := clientFactory.KubeClient().Apply(ctx, 
localRes.ResourceSpec, kube.KubeClientApplyOptions{
-               DefaultNamespace: releaseNamespace,
-               DryRun:           true,
-       })
-
-       installType, err := resourceInstallType(ctx, localRes, getObj, 
dryApplyObj, dryApplyErr)
+       installType, skippedByPolicy, err := resourceInstallType(ctx, localRes, 
getObj, dryApplyObj, dryApplyErr, resourcePolicies)
        if err != nil {
                return nil, fmt.Errorf("determine install type for resource %q: 
%w", localRes.IDHuman(), err)
        }
 
-       getMeta := spec.NewResourceMetaFromUnstructured(getObj, 
releaseNamespace, localRes.FilePath)
-       mustDeleteOnSuccess := mustDeleteOnSuccessfulDeploy(localRes, getMeta, 
installType, releaseNamespace)
-       trackReadiness := mustTrackReadiness(localRes, installType, true, 
prevRelFailed, mustDeleteOnSuccess)
+       mustDeleteOnSuccess := mustDeleteOnSuccessfulDeploy(localRes, getMeta, 
installType, releaseNamespace, skippedByPolicy)
+       trackReadiness := mustTrackReadiness(localRes, installType, getObj != 
nil, prevRelFailed, mustDeleteOnSuccess, skippedByPolicy)
 
        return lo.Map(stages, func(stg common.Stage, _ int) 
*InstallableResourceInfo {
                return &InstallableResourceInfo{
                        ResourceMeta:                   localRes.ResourceMeta,
                        DryApplyErr:                    dryApplyErr,
                        DryApplyResult:                 dryApplyObj,
+                       FailMode:                       localRes.FailMode,
                        GetResult:                      getObj,
                        LocalResource:                  localRes,
-                       MustDeleteOnFailedInstall:      
mustDeleteOnFailedDeploy(localRes, getMeta, installType, releaseNamespace, 
trackReadiness),
+                       MustDeleteOnFailedInstall:      
mustDeleteOnFailedDeploy(localRes, getMeta, installType, releaseNamespace, 
trackReadiness, skippedByPolicy),
                        MustDeleteOnSuccessfulInstall:  mustDeleteOnSuccess,
                        MustInstall:                    installType,
                        MustTrackReadiness:             trackReadiness,
@@ -358,7 +354,7 @@
                Stage:         stage,
        }
 
-       if localRes.KeepOnDelete || localRes.Ownership == 
common.OwnershipAnyone {
+       if lo.Contains(localRes.ResourcePolicies, 
common.ResourcePolicySkipDelete) || localRes.Ownership == 
common.OwnershipAnyone {
                return noDeleteInfo, nil
        }
 
@@ -380,10 +376,8 @@
 
        if err := resource.ValidateResourcePolicy(getMeta); err != nil {
                return noDeleteInfo, nil
-       } else {
-               if keep := resource.KeepOnDelete(getMeta, releaseNamespace); 
keep {
-                       return noDeleteInfo, nil
-               }
+       } else if lo.Contains(resource.ResourcePolicies(getMeta, 
releaseNamespace), common.ResourcePolicySkipDelete) {
+               return noDeleteInfo, nil
        }
 
        if orphaned(getMeta, releaseName, releaseNamespace) {
@@ -584,6 +578,46 @@
        return nil
 }
 
+func forceReadinessTrackingForReadyDependencyTargets(infos 
[]*InstallableResourceInfo) {
+       type readyMatcher struct {
+               matcher *spec.ResourceMatcher
+               stage   common.Stage
+       }
+
+       var readyMatchers []readyMatcher
+       for _, info := range infos {
+               for _, dep := range 
info.LocalResource.ManualInternalDependencies {
+                       if dep.ResourceState == common.ResourceStateReady {
+                               readyMatchers = append(readyMatchers, 
readyMatcher{matcher: dep.ResourceMatcher, stage: info.Stage})
+                       }
+               }
+       }
+
+       if len(readyMatchers) == 0 {
+               return
+       }
+
+       for _, info := range infos {
+               if spec.IsCRD(info.GroupVersionKind.GroupKind()) {
+                       continue
+               }
+
+               if info.MustInstall == ResourceInstallTypeNone && 
info.GetResult == nil {
+                       continue
+               }
+
+               matched := lo.SomeBy(readyMatchers, func(rm readyMatcher) bool {
+                       return rm.stage == info.Stage && 
rm.matcher.Match(info.ResourceMeta)
+               })
+               if !matched {
+                       continue
+               }
+
+               info.MustTrackReadiness = true
+               info.FailMode = multitrack.FailWholeDeployProcessImmediately
+       }
+}
+
 func getManagedFieldPathFromSpecPath(subPath []string) []string {
        path := make([]string, len(subPath))
 
@@ -676,16 +710,17 @@
                                return iterInfo.ID() == inf.ID() && 
inf.Iteration == iterInfo.Iteration-1
                        }))
 
-                       if prevIterInfo.MustDeleteOnSuccessfulInstall {
+                       if prevIterInfo.MustDeleteOnSuccessfulInstall && 
!lo.Contains(iterInfo.LocalResource.ResourcePolicies, 
common.ResourcePolicySkipCreate) {
                                iterInfo.MustInstall = ResourceInstallTypeCreate
                        }
                }
        }
 }
 
-func mustDeleteOnFailedDeploy(res *resource.InstallableResource, getMeta 
*spec.ResourceMeta, installType ResourceInstallType, releaseNamespace string, 
mustTrackReadiness bool) bool {
-       if !res.DeleteOnFailed ||
-               res.KeepOnDelete ||
+func mustDeleteOnFailedDeploy(res *resource.InstallableResource, getMeta 
*spec.ResourceMeta, installType ResourceInstallType, releaseNamespace string, 
mustTrackReadiness, skippedByPolicy bool) bool {
+       if skippedByPolicy ||
+               !res.DeleteOnFailed ||
+               lo.Contains(res.ResourcePolicies, 
common.ResourcePolicySkipDelete) ||
                installType == ResourceInstallTypeNone ||
                !mustTrackReadiness {
                return false
@@ -694,28 +729,26 @@
        if getMeta != nil {
                if err := resource.ValidateResourcePolicy(getMeta); err != nil {
                        return false
-               } else {
-                       if keep := resource.KeepOnDelete(getMeta, 
releaseNamespace); keep {
-                               return false
-                       }
+               } else if lo.Contains(resource.ResourcePolicies(getMeta, 
releaseNamespace), common.ResourcePolicySkipDelete) {
+                       return false
                }
        }
 
        return true
 }
 
-func mustDeleteOnSuccessfulDeploy(localRes *resource.InstallableResource, 
getMeta *spec.ResourceMeta, installType ResourceInstallType, releaseNamespace 
string) bool {
-       if !localRes.DeleteOnSucceeded || localRes.KeepOnDelete {
+func mustDeleteOnSuccessfulDeploy(localRes *resource.InstallableResource, 
getMeta *spec.ResourceMeta, installType ResourceInstallType, releaseNamespace 
string, skippedByPolicy bool) bool {
+       if skippedByPolicy ||
+               !localRes.DeleteOnSucceeded ||
+               lo.Contains(localRes.ResourcePolicies, 
common.ResourcePolicySkipDelete) {
                return false
        }
 
        if getMeta != nil {
                if err := resource.ValidateResourcePolicy(getMeta); err != nil {
                        return false
-               } else {
-                       if keep := resource.KeepOnDelete(getMeta, 
releaseNamespace); keep {
-                               return false
-                       }
+               } else if lo.Contains(resource.ResourcePolicies(getMeta, 
releaseNamespace), common.ResourcePolicySkipDelete) {
+                       return false
                }
        }
 
@@ -726,8 +759,9 @@
        return true
 }
 
-func mustTrackReadiness(res *resource.InstallableResource, resInstallType 
ResourceInstallType, exists, prevRelFailed, mustDeleteOnSuccessfulInstall bool) 
bool {
-       if spec.IsCRD(res.Unstruct.GroupVersionKind().GroupKind()) ||
+func mustTrackReadiness(res *resource.InstallableResource, resInstallType 
ResourceInstallType, exists, prevRelFailed, mustDeleteOnSuccessfulInstall, 
skippedByPolicy bool) bool {
+       if skippedByPolicy ||
+               spec.IsCRD(res.Unstruct.GroupVersionKind().GroupKind()) ||
                res.TrackTerminationMode == multitrack.NonBlocking {
                return false
        }
@@ -797,18 +831,39 @@
        return newManagedFields, newOursEntry, changed
 }
 
-func resourceInstallType(ctx context.Context, localRes 
*resource.InstallableResource, getObj, dryApplyObj *unstructured.Unstructured, 
dryApplyErr error) (ResourceInstallType, error) {
+func resourceInstallType(ctx context.Context, localRes 
*resource.InstallableResource, getObj, dryApplyObj *unstructured.Unstructured, 
dryApplyErr error, resourcePolicies []common.ResourcePolicy) (installType 
ResourceInstallType, skippedByPolicy bool, err error) {
+       skipCreate := lo.Contains(resourcePolicies, 
common.ResourcePolicySkipCreate)
+       skipUpdate := lo.Contains(resourcePolicies, 
common.ResourcePolicySkipUpdate)
+       skipRecreate := lo.Contains(resourcePolicies, 
common.ResourcePolicySkipRecreate)
+
+       if getObj == nil {
+               if skipCreate {
+                       return ResourceInstallTypeNone, true, nil
+               }
+
+               return ResourceInstallTypeCreate, false, nil
+       }
+
        isImmutable := dryApplyErr != nil && kube.IsImmutableErr(dryApplyErr)
+
+       if skipRecreate && (localRes.Recreate || (isImmutable && 
(localRes.RecreateOnImmutable || !localRes.Recreate))) {
+               return ResourceInstallTypeNone, true, nil
+       }
+
        if isImmutable && !localRes.Recreate && !localRes.RecreateOnImmutable {
-               return "", fmt.Errorf("immutable fields change in resource %q, 
but recreation is not requested: %w", localRes.IDHuman(), dryApplyErr)
+               return "", false, fmt.Errorf("immutable fields change in 
resource %q, but recreation is not requested: %w", localRes.IDHuman(), 
dryApplyErr)
        }
 
        if localRes.Recreate || (isImmutable && localRes.RecreateOnImmutable) {
-               return ResourceInstallTypeRecreate, nil
+               return ResourceInstallTypeRecreate, false, nil
        }
 
        if dryApplyErr != nil {
-               return ResourceInstallTypeApply, nil
+               if skipCreate || skipUpdate {
+                       return ResourceInstallTypeNone, true, nil
+               }
+
+               return ResourceInstallTypeApply, false, nil
        }
 
        diffableGetObj := spec.CleanUnstruct(getObj, spec.CleanUnstructOptions{
@@ -824,14 +879,18 @@
        })
 
        if patch, err := jsondiff.Compare(diffableGetObj, diffableDryApplyObj); 
err != nil {
-               return "", fmt.Errorf("compare live and dry-apply versions of 
resource %q: %w", localRes.IDHuman(), err)
+               return "", false, fmt.Errorf("compare live and dry-apply 
versions of resource %q: %w", localRes.IDHuman(), err)
        } else if len(patch) > 0 {
+               if skipUpdate {
+                       return ResourceInstallTypeNone, true, nil
+               }
+
                log.Default.Trace(ctx, "Get/DryApply patch for %q: %s", 
localRes.IDHuman(), patch.String())
 
-               return ResourceInstallTypeUpdate, nil
+               return ResourceInstallTypeUpdate, false, nil
        }
 
-       return ResourceInstallTypeNone, nil
+       return ResourceInstallTypeNone, false, nil
 }
 
 func stageDeleteOnSuccessfulInstall(shouldDelete bool, installStg 
common.Stage) common.Stage {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/plan/resource_info_test.go 
new/nelm-1.26.2/pkg/plan/resource_info_test.go
--- old/nelm-1.25.3/pkg/plan/resource_info_test.go      2026-06-24 
21:01:58.000000000 +0200
+++ new/nelm-1.26.2/pkg/plan/resource_info_test.go      2026-07-16 
10:38:47.000000000 +0200
@@ -91,7 +91,7 @@
                        },
                        input: func() (*resource.DeletableResource, 
common.DeployType) {
                                localRes := 
defaultDeletableResource(s.releaseName, s.releaseNamespace)
-                               localRes.KeepOnDelete = true
+                               localRes.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
 
                                return localRes, common.DeployTypeUninstall
                        },
@@ -429,6 +429,71 @@
                        },
                        name: `for non-existing resource, uninstall deploy`,
                },
+               {
+                       expect: func(localRes *resource.InstallableResource) 
[]*plan.InstallableResourceInfo {
+                               info := defaultInstallableResourceInfo(localRes)
+                               info.MustInstall = plan.ResourceInstallTypeNone
+                               info.MustTrackReadiness = false
+
+                               return []*plan.InstallableResourceInfo{info}
+                       },
+                       input: func() (*resource.InstallableResource, 
common.DeployType, bool) {
+                               localRes := 
defaultInstallableResource(s.releaseName, s.releaseNamespace)
+                               localRes.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipCreate}
+
+                               return localRes, common.DeployTypeInitial, false
+                       },
+                       name: `for non-existing resource with skip-create, 
initial deploy`,
+               },
+               {
+                       expect: func(localRes *resource.InstallableResource) 
[]*plan.InstallableResourceInfo {
+                               info := defaultInstallableResourceInfo(localRes)
+                               info.GetResult = 
defaultResourceSpec(s.releaseName, s.releaseNamespace).Unstruct
+                               info.DryApplyResult = 
updatedResourceSpec(&s.Suite, s.releaseName, s.releaseNamespace).Unstruct
+                               info.MustInstall = plan.ResourceInstallTypeNone
+                               info.MustTrackReadiness = false
+
+                               return []*plan.InstallableResourceInfo{info}
+                       },
+                       input: func() (*resource.InstallableResource, 
common.DeployType, bool) {
+                               localRes := 
updatedInstallableResource(&s.Suite, s.releaseName, s.releaseNamespace)
+                               localRes.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipUpdate}
+
+                               return localRes, common.DeployTypeInitial, false
+                       },
+                       name: `for outdated resource with skip-update, initial 
deploy`,
+                       prepare: func() {
+                               _, err := 
s.clientFactory.KubeClient().Create(context.Background(), 
defaultResourceSpec(s.releaseName, s.releaseNamespace), 
kube.KubeClientCreateOptions{
+                                       DefaultNamespace: s.releaseNamespace,
+                               })
+                               s.Require().NoError(err)
+                       },
+               },
+               {
+                       expect: func(localRes *resource.InstallableResource) 
[]*plan.InstallableResourceInfo {
+                               info := defaultInstallableResourceInfo(localRes)
+                               info.GetResult = 
defaultResourceSpec(s.releaseName, s.releaseNamespace).Unstruct
+                               info.DryApplyResult = 
updatedResourceSpec(&s.Suite, s.releaseName, s.releaseNamespace).Unstruct
+                               info.MustInstall = plan.ResourceInstallTypeNone
+                               info.MustTrackReadiness = false
+
+                               return []*plan.InstallableResourceInfo{info}
+                       },
+                       input: func() (*resource.InstallableResource, 
common.DeployType, bool) {
+                               localRes := 
updatedInstallableResource(&s.Suite, s.releaseName, s.releaseNamespace)
+                               localRes.Recreate = true
+                               localRes.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipRecreate}
+
+                               return localRes, common.DeployTypeInitial, false
+                       },
+                       name: `for outdated resource with skip-recreate, 
initial deploy`,
+                       prepare: func() {
+                               _, err := 
s.clientFactory.KubeClient().Create(context.Background(), 
defaultResourceSpec(s.releaseName, s.releaseNamespace), 
kube.KubeClientCreateOptions{
+                                       DefaultNamespace: s.releaseNamespace,
+                               })
+                               s.Require().NoError(err)
+                       },
+               },
        }
 
        for _, tc := range testCases {
@@ -567,6 +632,7 @@
                LocalResource:      localRes,
                MustInstall:        plan.ResourceInstallTypeCreate,
                MustTrackReadiness: true,
+               FailMode:           localRes.FailMode,
                Stage:              common.StageInstall,
        }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/resource/metadata.go 
new/nelm-1.26.2/pkg/resource/metadata.go
--- old/nelm-1.25.3/pkg/resource/metadata.go    2026-06-24 21:01:58.000000000 
+0200
+++ new/nelm-1.26.2/pkg/resource/metadata.go    2026-07-16 10:38:47.000000000 
+0200
@@ -26,27 +26,54 @@
        "github.com/werf/nelm/pkg/util"
 )
 
-func KeepOnDelete(meta *spec.ResourceMeta, releaseNamespace string) bool {
-       if spec.IsReleaseNamespace(meta.Name, meta.GroupVersionKind, 
releaseNamespace) {
-               return true
+func ResourcePolicies(meta *spec.ResourceMeta, releaseNamespace string) 
[]common.ResourcePolicy {
+       var policies []common.ResourcePolicy
+       if _, value, ok := 
spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, 
common.AnnotationKeyPatternWerfResourcePolicy); ok {
+               policies = parseResourcePolicies(value)
+       } else if _, value, ok := 
spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, 
common.AnnotationKeyPatternResourcePolicy); ok {
+               policies = parseResourcePolicies(value)
        }
 
-       _, value, found := 
spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, 
common.AnnotationKeyPatternResourcePolicy)
-       if !found {
-               return false
+       if spec.IsReleaseNamespace(meta.Name, meta.GroupVersionKind, 
releaseNamespace) && !lo.Contains(policies, common.ResourcePolicySkipDelete) {
+               policies = append(policies, common.ResourcePolicySkipDelete)
        }
 
-       return value == "keep"
+       return policies
 }
 
 func ValidateResourcePolicy(meta *spec.ResourceMeta) error {
+       if key, value, found := 
spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, 
common.AnnotationKeyPatternWerfResourcePolicy); found {
+               if value == "" {
+                       return fmt.Errorf("invalid value %q for annotation %q, 
expected non-empty string value", value, key)
+               }
+
+               for _, policy := range strings.Split(value, ",") {
+                       policy = strings.TrimSpace(policy)
+                       if policy == "" {
+                               return fmt.Errorf("invalid value %q for 
annotation %q, one of the comma-separated values is empty", value, key)
+                       }
+
+                       switch common.ResourcePolicy(policy) {
+                       case common.ResourcePolicySkipCreate,
+                               common.ResourcePolicySkipUpdate,
+                               common.ResourcePolicySkipRecreate,
+                               common.ResourcePolicySkipDelete,
+                               common.ResourcePolicyKeep:
+                       default:
+                               return fmt.Errorf("invalid unknown value %q for 
annotation %q", policy, key)
+                       }
+               }
+
+               return nil
+       }
+
        if key, value, found := 
spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, 
common.AnnotationKeyPatternResourcePolicy); found {
                if value == "" {
                        return fmt.Errorf("invalid value %q for annotation %q, 
expected non-empty string value", value, key)
                }
 
-               switch value {
-               case "keep":
+               switch common.ResourcePolicy(value) {
+               case common.ResourcePolicyKeep:
                default:
                        return fmt.Errorf("invalid unknown value %q for 
annotation %q", value, key)
                }
@@ -521,7 +548,7 @@
        return lo.Values(deps)
 }
 
-func manualInternalDeployDependencies(meta *spec.ResourceMeta) 
[]*InternalDependency {
+func manualInternalDeployDependencies(meta *spec.ResourceMeta, 
releaseNamespace string) []*InternalDependency {
        if spec.IsCRD(meta.GroupVersionKind.GroupKind()) {
                return nil
        }
@@ -555,6 +582,10 @@
                                depNamespace = valParts[2]
                        }
 
+                       if depNamespace == releaseNamespace {
+                               depNamespace = ""
+                       }
+
                        depName := valParts[len(valParts)-1]
 
                        dep := &InternalDependency{
@@ -585,7 +616,12 @@
 
                        var depNamespaces []string
                        if depNamespace, found := properties["namespace"]; 
found {
-                               depNamespaces = []string{depNamespace.(string)}
+                               ns := depNamespace.(string)
+                               if ns == releaseNamespace {
+                                       ns = ""
+                               }
+
+                               depNamespaces = []string{ns}
                        }
 
                        var depGroups []string
@@ -660,6 +696,25 @@
        }
 }
 
+func parseResourcePolicies(value string) []common.ResourcePolicy {
+       var policies []common.ResourcePolicy
+       for _, p := range strings.Split(value, ",") {
+               p = strings.TrimSpace(p)
+               if p == "" {
+                       continue
+               }
+
+               policy := common.ResourcePolicy(p)
+               if policy == common.ResourcePolicyKeep {
+                       policy = common.ResourcePolicySkipDelete
+               }
+
+               policies = append(policies, policy)
+       }
+
+       return policies
+}
+
 func showLogsOnlyForContainers(meta *spec.ResourceMeta) []string {
        _, value, found := 
spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, 
common.AnnotationKeyPatternShowLogsOnlyForContainers)
        if !found {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/resource/resource.go 
new/nelm-1.26.2/pkg/resource/resource.go
--- old/nelm-1.25.3/pkg/resource/resource.go    2026-06-24 21:01:58.000000000 
+0200
+++ new/nelm-1.26.2/pkg/resource/resource.go    2026-07-16 10:38:47.000000000 
+0200
@@ -26,10 +26,10 @@
        Ownership                              common.Ownership                
`json:"ownership"`
        Recreate                               bool                            
`json:"recreate"`
        RecreateOnImmutable                    bool                            
`json:"recreateOnImmutable"`
+       ResourcePolicies                       []common.ResourcePolicy         
`json:"resourcePolicies"`
        DefaultReplicasOnCreation              *int                            
`json:"defaultReplicasOnCreation,omitempty"`
        DeleteOnSucceeded                      bool                            
`json:"deleteOnSucceeded"`
        DeleteOnFailed                         bool                            
`json:"deleteOnFailed"`
-       KeepOnDelete                           bool                            
`json:"keepOnDelete"`
        FailMode                               multitrack.FailMode             
`json:"failMode"`
        FailuresAllowed                        int                             
`json:"failuresAllowed"`
        IgnoreReadinessProbeFailsForContainers map[string]time.Duration        
`json:"ignoreReadinessProbeFailsForContainers,omitempty"`
@@ -112,7 +112,7 @@
                return nil, fmt.Errorf("get external dependencies: %w", err)
        }
 
-       manIntDeps := manualInternalDeployDependencies(res.ResourceMeta)
+       manIntDeps := manualInternalDeployDependencies(res.ResourceMeta, 
releaseNamespace)
 
        otherUnstructs := lo.Map(otherResSpecs, func(resSpec 
*spec.ResourceSpec, _ int) *unstructured.Unstructured {
                return resSpec.Unstruct
@@ -130,7 +130,6 @@
                FailMode:                               
failMode(res.ResourceMeta),
                FailuresAllowed:                        
failuresAllowed(res.Unstruct),
                IgnoreReadinessProbeFailsForContainers: 
ignoreReadinessProbeFailsForContainers(res.ResourceMeta),
-               KeepOnDelete:                           
KeepOnDelete(res.ResourceMeta, releaseNamespace),
                LogRegex:                               
logRegex(res.ResourceMeta),
                LogRegexesForContainers:                
logRegexesForContainers(res.ResourceMeta),
                ManualInternalDependencies:             manIntDeps,
@@ -138,6 +137,7 @@
                Ownership:                              
ownership(res.ResourceMeta, releaseNamespace, res.StoreAs),
                Recreate:                               
recreate(res.ResourceMeta),
                RecreateOnImmutable:                    
recreateOnImmutable(res.ResourceMeta),
+               ResourcePolicies:                       
ResourcePolicies(res.ResourceMeta, releaseNamespace),
                ShowLogsOnlyForContainers:              
showLogsOnlyForContainers(res.ResourceMeta),
                ShowLogsOnlyForNumberOfReplicas:        
showLogsOnlyForNumberOfReplicas(res.ResourceMeta),
                ShowServiceMessages:                    
showServiceMessages(res.ResourceMeta),
@@ -164,19 +164,19 @@
 
        AutoInternalDependencies   []*InternalDependency
        DeletePropagation          metav1.DeletionPropagation
-       KeepOnDelete               bool
        ManualInternalDependencies []*InternalDependency
        Ownership                  common.Ownership
+       ResourcePolicies           []common.ResourcePolicy
 }
 
 // Construct a DeletableResource from a ResourceSpec. Must never contact the 
cluster, because
 // this is called even when no cluster access allowed.
 func NewDeletableResource(resourceSpec *spec.ResourceSpec, otherResourceSpecs 
[]*spec.ResourceSpec, releaseNamespace string, opts DeletableResourceOptions) 
*DeletableResource {
-       var keep bool
+       var policies []common.ResourcePolicy
        if err := ValidateResourcePolicy(resourceSpec.ResourceMeta); err != nil 
{
-               keep = true
+               policies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
        } else {
-               keep = KeepOnDelete(resourceSpec.ResourceMeta, releaseNamespace)
+               policies = ResourcePolicies(resourceSpec.ResourceMeta, 
releaseNamespace)
        }
 
        var owner common.Ownership
@@ -206,9 +206,9 @@
                ResourceMeta:               resourceSpec.ResourceMeta,
                AutoInternalDependencies:   
internalDeleteDependencies(resourceSpec.Unstruct, unstructList),
                DeletePropagation:          delPropagation,
-               KeepOnDelete:               keep,
                ManualInternalDependencies: manIntDeps,
                Ownership:                  owner,
+               ResourcePolicies:           policies,
        }
 }
 
@@ -281,7 +281,7 @@
                        return false
                }
 
-               if delRes.KeepOnDelete {
+               if lo.Contains(delRes.ResourcePolicies, 
common.ResourcePolicySkipDelete) {
                        return false
                }
 
@@ -371,3 +371,19 @@
 
        return instResources, delResources, nil
 }
+
+func ResolveResourcePolicies(localRes *InstallableResource, liveMeta 
*spec.ResourceMeta, releaseNamespace string) []common.ResourcePolicy {
+       if len(localRes.ResourcePolicies) > 0 || liveMeta == nil {
+               return localRes.ResourcePolicies
+       }
+
+       // TODO(major): in the next major keep/skip-delete should also be 
read/respected only from the manifest, not the cluster.
+       livePolicies := lo.Filter(ResourcePolicies(liveMeta, releaseNamespace), 
func(p common.ResourcePolicy, _ int) bool {
+               return p == common.ResourcePolicySkipDelete
+       })
+       if len(livePolicies) == 0 {
+               return nil
+       }
+
+       return livePolicies
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/nelm-1.25.3/pkg/resource/resource_namespace_dependency_ai_test.go 
new/nelm-1.26.2/pkg/resource/resource_namespace_dependency_ai_test.go
--- old/nelm-1.25.3/pkg/resource/resource_namespace_dependency_ai_test.go       
1970-01-01 01:00:00.000000000 +0100
+++ new/nelm-1.26.2/pkg/resource/resource_namespace_dependency_ai_test.go       
2026-07-16 10:38:47.000000000 +0200
@@ -0,0 +1,108 @@
+//go:build ai_tests
+
+package resource_test
+
+import (
+       "context"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+       "github.com/werf/nelm/pkg/common"
+       "github.com/werf/nelm/pkg/kube/fake"
+       "github.com/werf/nelm/pkg/resource"
+       "github.com/werf/nelm/pkg/resource/spec"
+)
+
+func TestAI_ManualDeployDependencyNormalizesReleaseNamespace(t *testing.T) {
+       const releaseNamespace = "test-namespace"
+
+       t.Run("modern syntax with release namespace normalizes to empty", 
func(t *testing.T) {
+               dep := manualDeployDep(t, releaseNamespace, map[string]string{
+                       "werf.io/deploy-dependency-target": 
"state=ready,kind=Deployment,group=apps,name=target,namespace=" + 
releaseNamespace,
+               })
+
+               require.Equal(t, common.ResourceStateReady, dep.ResourceState)
+               require.Equal(t, []string{""}, dep.Namespaces)
+       })
+
+       t.Run("modern syntax with foreign namespace kept literal", func(t 
*testing.T) {
+               dep := manualDeployDep(t, releaseNamespace, map[string]string{
+                       "werf.io/deploy-dependency-target": 
"state=ready,kind=Deployment,group=apps,name=target,namespace=other-ns",
+               })
+
+               require.Equal(t, []string{"other-ns"}, dep.Namespaces)
+       })
+
+       t.Run("modern syntax with omitted namespace yields empty list", func(t 
*testing.T) {
+               dep := manualDeployDep(t, releaseNamespace, map[string]string{
+                       "werf.io/deploy-dependency-target": 
"state=ready,kind=Deployment,group=apps,name=target",
+               })
+
+               require.Empty(t, dep.Namespaces)
+       })
+
+       t.Run("legacy syntax with release namespace normalizes to empty", 
func(t *testing.T) {
+               dep := manualDeployDep(t, releaseNamespace, map[string]string{
+                       "target.dependency.werf.io": "apps/v1:Deployment:" + 
releaseNamespace + ":target",
+               })
+
+               require.Equal(t, common.ResourceStatePresent, dep.ResourceState)
+               require.Equal(t, []string{""}, dep.Namespaces)
+       })
+
+       t.Run("legacy syntax with foreign namespace kept literal", func(t 
*testing.T) {
+               dep := manualDeployDep(t, releaseNamespace, map[string]string{
+                       "target.dependency.werf.io": 
"apps/v1:Deployment:other-ns:target",
+               })
+
+               require.Equal(t, []string{"other-ns"}, dep.Namespaces)
+       })
+
+       t.Run("legacy syntax with omitted namespace keeps single empty-string 
entry", func(t *testing.T) {
+               dep := manualDeployDep(t, releaseNamespace, map[string]string{
+                       "target.dependency.werf.io": 
"apps/v1:Deployment:target",
+               })
+
+               require.Equal(t, []string{""}, dep.Namespaces)
+       })
+}
+
+func manualDeployDep(t *testing.T, releaseNamespace string, annotations 
map[string]string) *resource.InternalDependency {
+       t.Helper()
+
+       resSpec := newDependentConfigMapSpec(releaseNamespace, annotations)
+
+       clientFactory, err := fake.NewClientFactory(context.Background())
+       require.NoError(t, err)
+
+       res, err := resource.NewInstallableResource(resSpec, nil, 
releaseNamespace, clientFactory, resource.InstallableResourceOptions{})
+       require.NoError(t, err)
+       require.Len(t, res.ManualInternalDependencies, 1)
+
+       return res.ManualInternalDependencies[0]
+}
+
+func newDependentConfigMapSpec(releaseNamespace string, annotations 
map[string]string) *spec.ResourceSpec {
+       meta := map[string]interface{}{
+               "name":      "dependent",
+               "namespace": releaseNamespace,
+       }
+       if len(annotations) > 0 {
+               anns := map[string]interface{}{}
+               for k, v := range annotations {
+                       anns[k] = v
+               }
+               meta["annotations"] = anns
+       }
+
+       return spec.NewResourceSpec(&unstructured.Unstructured{
+               Object: map[string]interface{}{
+                       "apiVersion": "v1",
+                       "kind":       "ConfigMap",
+                       "metadata":   meta,
+                       "data":       map[string]interface{}{"key": "value"},
+               },
+       }, releaseNamespace, spec.ResourceSpecOptions{})
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/resource/resource_policy_test.go 
new/nelm-1.26.2/pkg/resource/resource_policy_test.go
--- old/nelm-1.25.3/pkg/resource/resource_policy_test.go        1970-01-01 
01:00:00.000000000 +0100
+++ new/nelm-1.26.2/pkg/resource/resource_policy_test.go        2026-07-16 
10:38:47.000000000 +0200
@@ -0,0 +1,197 @@
+package resource_test
+
+import (
+       "context"
+       "testing"
+
+       "github.com/samber/lo"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+       "github.com/werf/nelm/pkg/common"
+       "github.com/werf/nelm/pkg/kube/fake"
+       "github.com/werf/nelm/pkg/resource"
+       "github.com/werf/nelm/pkg/resource/spec"
+)
+
+const resourcePolicyTestNamespace = "test-namespace"
+
+func TestResolveResourcePolicies(t *testing.T) {
+       tests := []struct {
+               name        string
+               chart, live map[string]string
+               nilLive     bool
+               want        []common.ResourcePolicy
+       }{
+               {
+                       name:    "no policies",
+                       nilLive: true,
+               },
+               {
+                       name:    "chart skip-create only",
+                       chart:   map[string]string{"werf.io/resource-policy": 
"skip-create"},
+                       nilLive: true,
+                       want:    
[]common.ResourcePolicy{common.ResourcePolicySkipCreate},
+               },
+               {
+                       name:    "chart all skips",
+                       chart:   map[string]string{"werf.io/resource-policy": 
"skip-create,skip-update,skip-recreate"},
+                       nilLive: true,
+                       want:    
[]common.ResourcePolicy{common.ResourcePolicySkipCreate, 
common.ResourcePolicySkipUpdate, common.ResourcePolicySkipRecreate},
+               },
+               {
+                       name: "live skip-update dropped when chart absent",
+                       live: map[string]string{"werf.io/resource-policy": 
"skip-update"},
+               },
+               {
+                       name: "live install skips dropped when chart absent",
+                       live: map[string]string{"werf.io/resource-policy": 
"skip-create,skip-update,skip-recreate"},
+               },
+               {
+                       name: "live skip-delete retained when chart absent",
+                       live: map[string]string{"werf.io/resource-policy": 
"skip-delete"},
+                       want: 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete},
+               },
+               {
+                       name: "live werf.io keep retained as skip-delete when 
chart absent",
+                       live: map[string]string{"werf.io/resource-policy": 
"keep"},
+                       want: 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete},
+               },
+               {
+                       name: "live helm.sh keep retained as skip-delete when 
chart absent",
+                       live: map[string]string{"helm.sh/resource-policy": 
"keep"},
+                       want: 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete},
+               },
+               {
+                       name: "live mixed policies filtered to skip-delete when 
chart absent",
+                       live: map[string]string{"werf.io/resource-policy": 
"skip-update,skip-delete"},
+                       want: 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete},
+               },
+               {
+                       name:  "chart present takes precedence over live (no 
merge)",
+                       chart: map[string]string{"werf.io/resource-policy": 
"skip-update"},
+                       live:  map[string]string{"werf.io/resource-policy": 
"skip-delete"},
+                       want:  
[]common.ResourcePolicy{common.ResourcePolicySkipUpdate},
+               },
+               {
+                       name:  "chart helm.sh keep present suppresses live 
werf.io skips",
+                       chart: map[string]string{"helm.sh/resource-policy": 
"keep"},
+                       live:  map[string]string{"werf.io/resource-policy": 
"skip-create"},
+                       want:  
[]common.ResourcePolicy{common.ResourcePolicySkipDelete},
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       var liveMeta *spec.ResourceMeta
+                       if !tt.nilLive {
+                               liveMeta = resourcePolicyMeta(tt.live)
+                       }
+
+                       localRes := chartInstallableResource(t, tt.chart)
+                       assert.Equal(t, tt.want, 
resource.ResolveResourcePolicies(localRes, liveMeta, 
resourcePolicyTestNamespace))
+               })
+       }
+}
+
+func TestResourcePoliciesSkipDelete(t *testing.T) {
+       tests := []struct {
+               name        string
+               annotations map[string]string
+               want        bool
+       }{
+               {name: "no annotation", annotations: nil, want: false},
+               {name: "helm.sh keep", annotations: 
map[string]string{"helm.sh/resource-policy": "keep"}, want: true},
+               {name: "werf.io keep", annotations: 
map[string]string{"werf.io/resource-policy": "keep"}, want: true},
+               {name: "werf.io skip-delete", annotations: 
map[string]string{"werf.io/resource-policy": "skip-delete"}, want: true},
+               {name: "werf.io skip-create only", annotations: 
map[string]string{"werf.io/resource-policy": "skip-create"}, want: false},
+               {name: "werf.io skip-delete among others", annotations: 
map[string]string{"werf.io/resource-policy": "skip-update,skip-delete"}, want: 
true},
+               {name: "werf.io overrides helm.sh keep", annotations: 
map[string]string{"helm.sh/resource-policy": "keep", "werf.io/resource-policy": 
"skip-create"}, want: false},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got := 
lo.Contains(resource.ResourcePolicies(resourcePolicyMeta(tt.annotations), 
resourcePolicyTestNamespace), common.ResourcePolicySkipDelete)
+                       assert.Equal(t, tt.want, got)
+               })
+       }
+}
+
+func TestValidateResourcePolicy(t *testing.T) {
+       tests := []struct {
+               name        string
+               annotations map[string]string
+               wantErr     bool
+       }{
+               {name: "no annotation", annotations: nil},
+               {name: "helm.sh keep", annotations: 
map[string]string{"helm.sh/resource-policy": "keep"}},
+               {name: "helm.sh empty is invalid", annotations: 
map[string]string{"helm.sh/resource-policy": ""}, wantErr: true},
+               {name: "helm.sh skip-create is invalid", annotations: 
map[string]string{"helm.sh/resource-policy": "skip-create"}, wantErr: true},
+               {name: "werf.io keep", annotations: 
map[string]string{"werf.io/resource-policy": "keep"}},
+               {name: "werf.io skip-delete", annotations: 
map[string]string{"werf.io/resource-policy": "skip-delete"}},
+               {name: "werf.io all directives", annotations: 
map[string]string{"werf.io/resource-policy": 
"skip-create,skip-update,skip-recreate,skip-delete,keep"}},
+               {name: "werf.io with spaces", annotations: 
map[string]string{"werf.io/resource-policy": "skip-create, skip-update"}},
+               {name: "werf.io empty is invalid", annotations: 
map[string]string{"werf.io/resource-policy": ""}, wantErr: true},
+               {name: "werf.io empty segment is invalid", annotations: 
map[string]string{"werf.io/resource-policy": "skip-create,,skip-update"}, 
wantErr: true},
+               {name: "werf.io unknown value is invalid", annotations: 
map[string]string{"werf.io/resource-policy": "skip-create,bogus"}, wantErr: 
true},
+               // werf.io fully overrides helm.sh: a bad legacy value must not 
reject the resource.
+               {name: "werf.io present ignores bad helm.sh value", 
annotations: map[string]string{"helm.sh/resource-policy": "bogus", 
"werf.io/resource-policy": "skip-update"}},
+               {name: "werf.io invalid still rejected even with valid 
helm.sh", annotations: map[string]string{"helm.sh/resource-policy": "keep", 
"werf.io/resource-policy": "bogus"}, wantErr: true},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       err := 
resource.ValidateResourcePolicy(resourcePolicyMeta(tt.annotations))
+                       if tt.wantErr {
+                               assert.Error(t, err)
+                       } else {
+                               assert.NoError(t, err)
+                       }
+               })
+       }
+}
+
+func chartInstallableResource(t *testing.T, annotations map[string]string) 
*resource.InstallableResource {
+       t.Helper()
+
+       obj := &unstructured.Unstructured{
+               Object: map[string]interface{}{
+                       "apiVersion": "v1",
+                       "kind":       "ConfigMap",
+                       "metadata": map[string]interface{}{
+                               "name": "test-configmap",
+                       },
+               },
+       }
+
+       resSpec := spec.NewResourceSpec(obj, resourcePolicyTestNamespace, 
spec.ResourceSpecOptions{})
+       if len(annotations) > 0 {
+               resSpec.SetAnnotations(annotations)
+       }
+
+       clientFactory, err := fake.NewClientFactory(context.Background())
+       require.NoError(t, err)
+
+       localRes, err := resource.NewInstallableResource(resSpec, nil, 
resourcePolicyTestNamespace, clientFactory, 
resource.InstallableResourceOptions{})
+       require.NoError(t, err)
+
+       return localRes
+}
+
+func resourcePolicyMeta(annotations map[string]string) *spec.ResourceMeta {
+       obj := &unstructured.Unstructured{
+               Object: map[string]interface{}{
+                       "apiVersion": "v1",
+                       "kind":       "ConfigMap",
+                       "metadata": map[string]interface{}{
+                               "name": "test-configmap",
+                       },
+               },
+       }
+       if len(annotations) > 0 {
+               obj.SetAnnotations(annotations)
+       }
+
+       return spec.NewResourceMetaFromUnstructured(obj, 
resourcePolicyTestNamespace, "")
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/pkg/resource/resource_test.go 
new/nelm-1.26.2/pkg/resource/resource_test.go
--- old/nelm-1.25.3/pkg/resource/resource_test.go       2026-06-24 
21:01:58.000000000 +0200
+++ new/nelm-1.26.2/pkg/resource/resource_test.go       2026-07-16 
10:38:47.000000000 +0200
@@ -1172,7 +1172,7 @@
                {
                        expect: func(resSpec *spec.ResourceSpec) 
*resource.InstallableResource {
                                res := defaultInstallableResource(resSpec)
-                               res.KeepOnDelete = true
+                               res.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
 
                                return res
                        },
@@ -1186,6 +1186,80 @@
                        },
                        name: `for resource with 
helm.sh/resource-policy="keep"`,
                },
+               {
+                       expect: func(resSpec *spec.ResourceSpec) 
*resource.InstallableResource {
+                               res := defaultInstallableResource(resSpec)
+                               res.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
+
+                               return res
+                       },
+                       input: func() *spec.ResourceSpec {
+                               resSpec := 
defaultResourceSpec(s.releaseNamespace)
+                               
resSpec.SetAnnotations(lo.Assign(resSpec.Annotations, map[string]string{
+                                       "werf.io/resource-policy": "keep",
+                               }))
+
+                               return resSpec
+                       },
+                       name: `for resource with 
werf.io/resource-policy="keep"`,
+               },
+               {
+                       expect: func(resSpec *spec.ResourceSpec) 
*resource.InstallableResource {
+                               res := defaultInstallableResource(resSpec)
+                               res.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
+
+                               return res
+                       },
+                       input: func() *spec.ResourceSpec {
+                               resSpec := 
defaultResourceSpec(s.releaseNamespace)
+                               
resSpec.SetAnnotations(lo.Assign(resSpec.Annotations, map[string]string{
+                                       "werf.io/resource-policy": 
"skip-delete",
+                               }))
+
+                               return resSpec
+                       },
+                       name: `for resource with 
werf.io/resource-policy="skip-delete"`,
+               },
+               {
+                       expect: func(resSpec *spec.ResourceSpec) 
*resource.InstallableResource {
+                               res := defaultInstallableResource(resSpec)
+                               res.ResourcePolicies = []common.ResourcePolicy{
+                                       common.ResourcePolicySkipCreate,
+                                       common.ResourcePolicySkipUpdate,
+                                       common.ResourcePolicySkipRecreate,
+                                       common.ResourcePolicySkipDelete,
+                               }
+
+                               return res
+                       },
+                       input: func() *spec.ResourceSpec {
+                               resSpec := 
defaultResourceSpec(s.releaseNamespace)
+                               
resSpec.SetAnnotations(lo.Assign(resSpec.Annotations, map[string]string{
+                                       "werf.io/resource-policy": 
"skip-create, skip-update, skip-recreate, skip-delete",
+                               }))
+
+                               return resSpec
+                       },
+                       name: `for resource with werf.io/resource-policy 
listing all skip directives`,
+               },
+               {
+                       expect: func(resSpec *spec.ResourceSpec) 
*resource.InstallableResource {
+                               res := defaultInstallableResource(resSpec)
+                               res.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipUpdate}
+
+                               return res
+                       },
+                       input: func() *spec.ResourceSpec {
+                               resSpec := 
defaultResourceSpec(s.releaseNamespace)
+                               
resSpec.SetAnnotations(lo.Assign(resSpec.Annotations, map[string]string{
+                                       "helm.sh/resource-policy": "keep",
+                                       "werf.io/resource-policy": 
"skip-update",
+                               }))
+
+                               return resSpec
+                       },
+                       name: `for resource where werf.io/resource-policy 
overrides helm.sh/resource-policy`,
+               },
        }
 
        for _, tc := range testCases {
@@ -1554,7 +1628,7 @@
 func defaultReleaseNamespaceDeletableResource(resSpec *spec.ResourceSpec) 
*resource.DeletableResource {
        res := defaultDeletableResource(resSpec.ResourceMeta)
        res.Ownership = common.OwnershipAnyone
-       res.KeepOnDelete = true
+       res.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
 
        return res
 }
@@ -1562,7 +1636,7 @@
 func defaultReleaseNamespaceInstallableResource(resSpec *spec.ResourceSpec) 
*resource.InstallableResource {
        res := defaultInstallableResource(resSpec)
        res.Ownership = common.OwnershipAnyone
-       res.KeepOnDelete = true
+       res.ResourcePolicies = 
[]common.ResourcePolicy{common.ResourcePolicySkipDelete}
        res.DeployConditions = map[common.On][]common.Stage{
                common.InstallOnInstall:  {common.StagePrePreInstall},
                common.InstallOnUpgrade:  {common.StagePrePreInstall},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.25.3/trdl_channels.yaml 
new/nelm-1.26.2/trdl_channels.yaml
--- old/nelm-1.25.3/trdl_channels.yaml  2026-06-24 21:01:58.000000000 +0200
+++ new/nelm-1.26.2/trdl_channels.yaml  2026-07-16 10:38:47.000000000 +0200
@@ -2,12 +2,12 @@
   - name: "1"
     channels:
       - name: alpha
-        version: 1.25.2
+        version: 1.26.0
       - name: beta
-        version: 1.25.0
+        version: 1.25.3
       - name: ea
-        version: 1.24.3
+        version: 1.25.0
       - name: stable
-        version: 1.24.1
+        version: 1.24.3
       - name: rock-solid
-        version: 1.24.0
+        version: 1.24.1

++++++ nelm.obsinfo ++++++
--- /var/tmp/diff_new_pack.YknDgy/_old  2026-09-21 12:08:03.998290952 +0200
+++ /var/tmp/diff_new_pack.YknDgy/_new  2026-09-21 12:08:04.003291161 +0200
@@ -1,5 +1,5 @@
 name: nelm
-version: 1.25.3
-mtime: 1782327718
-commit: 5d3d03cb0b97bdb6df7a5b22320bfbf53edafdb4
+version: 1.26.2
+mtime: 1784191127
+commit: 6505774f6217ec78612d8ca6ccad1e5b3e2cea7f
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/nelm/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.nelm.new.383539/vendor.tar.gz differ: char 130, 
line 2

Reply via email to