Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kargo-cli for openSUSE:Factory 
checked in at 2026-02-18 17:13:07
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kargo-cli (Old)
 and      /work/SRC/openSUSE:Factory/.kargo-cli.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kargo-cli"

Wed Feb 18 17:13:07 2026 rev:45 rq:1333760 version:1.9.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/kargo-cli/kargo-cli.changes      2026-02-03 
21:30:55.511200534 +0100
+++ /work/SRC/openSUSE:Factory/.kargo-cli.new.1977/kargo-cli.changes    
2026-02-18 17:13:28.353815099 +0100
@@ -1,0 +2,10 @@
+Wed Feb 18 08:09:08 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.9.3:
+  * CLI-related changes
+    - chore(backport release-1.9): fix(cli): correct promotion
+      response unmarshaling (#5702)
+  Dependencies:
+  * chore(backport release-1.9): fix: bump go-githubauth (#5716)
+
+-------------------------------------------------------------------

Old:
----
  kargo-cli-1.9.2.obscpio

New:
----
  kargo-cli-1.9.3.obscpio

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

Other differences:
------------------
++++++ kargo-cli.spec ++++++
--- /var/tmp/diff_new_pack.Oo2knL/_old  2026-02-18 17:13:29.945881350 +0100
+++ /var/tmp/diff_new_pack.Oo2knL/_new  2026-02-18 17:13:29.945881350 +0100
@@ -19,7 +19,7 @@
 %define executable_name kargo
 
 Name:           kargo-cli
-Version:        1.9.2
+Version:        1.9.3
 Release:        0
 Summary:        CLI for the Kubernetes Application lifecycle orchestration
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.Oo2knL/_old  2026-02-18 17:13:29.989883181 +0100
+++ /var/tmp/diff_new_pack.Oo2knL/_new  2026-02-18 17:13:29.993883348 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/akuity/kargo</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v1.9.2</param>
+    <param name="revision">v1.9.3</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.Oo2knL/_old  2026-02-18 17:13:30.017884347 +0100
+++ /var/tmp/diff_new_pack.Oo2knL/_new  2026-02-18 17:13:30.025884679 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/akuity/kargo</param>
-              <param 
name="changesrevision">141a6cd104f855d404119b78c5ae2646c66ea9e4</param></service></servicedata>
+              <param 
name="changesrevision">155c6852ffbffa2902f18e6c7add91a846e8d344</param></service></servicedata>
 (No newline at EOF)
 

++++++ kargo-cli-1.9.2.obscpio -> kargo-cli-1.9.3.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/docs/docs/40-operator-guide/40-security/30-access-controls.md
 
new/kargo-cli-1.9.3/docs/docs/40-operator-guide/40-security/30-access-controls.md
--- 
old/kargo-cli-1.9.2/docs/docs/40-operator-guide/40-security/30-access-controls.md
   2026-02-02 13:44:17.000000000 +0100
+++ 
new/kargo-cli-1.9.3/docs/docs/40-operator-guide/40-security/30-access-controls.md
   2026-02-17 18:59:04.000000000 +0100
@@ -79,7 +79,7 @@
 
 ServiceAccount resources may be mapped to users via the
 `rbac.kargo.akuity.io/claims` annotation, whose value is a string 
representation
-of a JSON or YAML object with claim names as its keys and lists of claim values
+of a JSON object with claim names as its keys and lists of claim values
 as its values.
 
 In the following example, the `ServiceAccount` resource is mapped to all of:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/docs/docs/50-user-guide/50-security/20-access-controls/index.md
 
new/kargo-cli-1.9.3/docs/docs/50-user-guide/50-security/20-access-controls/index.md
--- 
old/kargo-cli-1.9.2/docs/docs/50-user-guide/50-security/20-access-controls/index.md
 2026-02-02 13:44:17.000000000 +0100
+++ 
new/kargo-cli-1.9.3/docs/docs/50-user-guide/50-security/20-access-controls/index.md
 2026-02-17 18:59:04.000000000 +0100
@@ -77,7 +77,7 @@
 
 ServiceAccount resources may be mapped to users via the
 `rbac.kargo.akuity.io/claims` annotation, whose value is a string 
representation
-of a JSON or YAML object with claim names as its keys and lists of claim values
+of a JSON object with claim names as its keys and lists of claim values
 as its values.
 
 In the following example, the `ServiceAccount` resource is mapped to all of:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/docs/docs/50-user-guide/60-reference-docs/80-webhook-receivers/generic.md
 
new/kargo-cli-1.9.3/docs/docs/50-user-guide/60-reference-docs/80-webhook-receivers/generic.md
--- 
old/kargo-cli-1.9.2/docs/docs/50-user-guide/60-reference-docs/80-webhook-receivers/generic.md
       2026-02-02 13:44:17.000000000 +0100
+++ 
new/kargo-cli-1.9.3/docs/docs/50-user-guide/60-reference-docs/80-webhook-receivers/generic.md
       2026-02-17 18:59:04.000000000 +0100
@@ -96,8 +96,8 @@
       generic:
         secretRef:
           name: wh-secret
-      actions:
-        - actionType: Refresh
+        actions:
+          - actionType: Refresh
 ```
 
 :::note
@@ -126,9 +126,9 @@
       generic:
         secretRef:
           name: wh-secret
-      actions:
-        - actionType: Refresh
-          whenExpression: "request.header("X-Event-Type") == 'push'"
+        actions:
+          - actionType: Refresh
+            whenExpression: "request.header("X-Event-Type") == 'push'"
 ```
 
 :::note
@@ -174,12 +174,12 @@
       generic:
         secretRef:
           name: wh-secret
-      actions:
-        - actionType: Refresh
-          whenExpression: "request.header('X-Event-Type') == 'push'"
-          targetSelectionCriteria:
-            - kind: Warehouse
-              name: my-warehouse
+        actions:
+          - actionType: Refresh
+            whenExpression: "request.header('X-Event-Type') == 'push'"
+            targetSelectionCriteria:
+              - kind: Warehouse
+                name: my-warehouse
 ```
 
 The following example depicts `targetSelectionCriteria` that selects
@@ -198,12 +198,12 @@
       generic:
         secretRef:
           name: wh-secret
-      actions:
-        - actionType: Refresh
-          whenExpression: "request.header('X-Event-Type') == 'push'"
-          targetSelectionCriteria:
-            - kind: Warehouse
-              name: "${{ normalizeGit(request.body.repository.name) }}"
+        actions:
+          - actionType: Refresh
+            whenExpression: "request.header('X-Event-Type') == 'push'"
+            targetSelectionCriteria:
+              - kind: Warehouse
+                name: "${{ normalizeGit(request.body.repository.name) }}"
 ```
 
 ##### By Labels
@@ -223,14 +223,14 @@
       generic:
         secretRef:
           name: wh-secret
-      actions:
-        - actionType: Refresh
-          whenExpression: "request.header('X-Event-Type') == 'push'"
-          targetSelectionCriteria:
-            - kind: Warehouse
-              labelSelector:
-                matchLabels:
-                  environment: prod
+        actions:
+          - actionType: Refresh
+            whenExpression: "request.header('X-Event-Type') == 'push'"
+            targetSelectionCriteria:
+              - kind: Warehouse
+                labelSelector:
+                  matchLabels:
+                    environment: prod
 ```
 
 The following example depicts `targetSelectionCriteria` that selects
@@ -249,16 +249,16 @@
       generic:
         secretRef:
           name: wh-secret
-      actions:
-        - actionType: Refresh
-          whenExpression: "request.header('X-Event-Type') == 'push'"
-          targetSelectionCriteria:
-            - kind: Warehouse
-              labelSelector:
-                matchExpressions:
-                  - key: service
-                    operator: In
-                    values: ["ui", "api"]
+        actions:
+          - actionType: Refresh
+            whenExpression: "request.header('X-Event-Type') == 'push'"
+            targetSelectionCriteria:
+              - kind: Warehouse
+                labelSelector:
+                  matchExpressions:
+                    - key: service
+                      operator: In
+                      values: ["ui", "api"]
 ```
 
 ##### By Values in an Index
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/go.mod new/kargo-cli-1.9.3/go.mod
--- old/kargo-cli-1.9.2/go.mod  2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/go.mod  2026-02-17 18:59:04.000000000 +0100
@@ -50,7 +50,7 @@
        github.com/google/uuid v1.6.0
        github.com/hashicorp/go-cleanhttp v0.5.2
        github.com/hashicorp/golang-lru/v2 v2.0.7
-       github.com/jferrl/go-githubauth v1.5.0
+       github.com/jferrl/go-githubauth v1.5.1
        github.com/kelseyhightower/envconfig v1.4.0
        github.com/klauspost/compress v1.18.3
        github.com/ktrysmt/go-bitbucket v0.9.87
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/go.sum new/kargo-cli-1.9.3/go.sum
--- old/kargo-cli-1.9.2/go.sum  2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/go.sum  2026-02-17 18:59:04.000000000 +0100
@@ -344,8 +344,8 @@
 github.com/inconshreveable/mousetrap v1.1.0/go.mod 
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 
h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod 
h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jferrl/go-githubauth v1.5.0 
h1:0zv6YqxGwtu2pjtb1DP2vaPVhdsIlyy4AhrjWryJTY8=
-github.com/jferrl/go-githubauth v1.5.0/go.mod 
h1:dwyfWjg9p59UvnSVevlPGGiVfVluPgezLlHBMLD5qs0=
+github.com/jferrl/go-githubauth v1.5.1 
h1:otHMf7Q6+Hw98fEznIUewsrhayXQqXinhNLc7uqYbco=
+github.com/jferrl/go-githubauth v1.5.1/go.mod 
h1:/TwNj2nXg/u0wrTnz8+BjJDThDKaScqsczu7Ryj+v2s=
 github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
 github.com/jmoiron/sqlx v1.4.0/go.mod 
h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
 github.com/josharian/intern v1.0.0 
h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/cli/cmd/promote/promote.go 
new/kargo-cli-1.9.3/pkg/cli/cmd/promote/promote.go
--- old/kargo-cli-1.9.2/pkg/cli/cmd/promote/promote.go  2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/cli/cmd/promote/promote.go  2026-02-17 
18:59:04.000000000 +0100
@@ -212,10 +212,14 @@
                if err != nil {
                        return fmt.Errorf("marshal promotion: %w", err)
                }
-               promo := &kargoapi.Promotion{}
-               if err = json.Unmarshal(promoJSON, promo); err != nil {
+               // The response is {"promotion": {...}}
+               var result struct {
+                       Promotion *kargoapi.Promotion `json:"promotion"`
+               }
+               if err = json.Unmarshal(promoJSON, &result); err != nil {
                        return fmt.Errorf("unmarshal promotion: %w", err)
                }
+               promo := result.Promotion
                if o.Wait {
                        if err = o.waitForPromotion(ctx, nil, promo); err != 
nil {
                                return fmt.Errorf("wait for promotion: %w", err)
@@ -237,14 +241,18 @@
                if err != nil {
                        return err
                }
-               var promotions []*kargoapi.Promotion
                promotionsJSON, err := json.Marshal(res.Payload)
                if err != nil {
                        return fmt.Errorf("marshal promotions: %w", err)
                }
-               if err = json.Unmarshal(promotionsJSON, &promotions); err != 
nil {
+               // The response is {"promotions": [...]}
+               var result struct {
+                       Promotions []*kargoapi.Promotion `json:"promotions"`
+               }
+               if err = json.Unmarshal(promotionsJSON, &result); err != nil {
                        return fmt.Errorf("unmarshal promotions: %w", err)
                }
+               promotions := result.Promotions
                if o.Wait {
                        if err = o.waitForPromotions(ctx, promotions...); err 
!= nil {
                                return fmt.Errorf("wait for promotion: %w", err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/credentials/github/app.go 
new/kargo-cli-1.9.3/pkg/credentials/github/app.go
--- old/kargo-cli-1.9.2/pkg/credentials/github/app.go   2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/credentials/github/app.go   2026-02-17 
18:59:04.000000000 +0100
@@ -280,7 +280,7 @@
        if len(parts) < 5 {
                return ""
        }
-       return strings.TrimSuffix(parts[len(parts)-1], ".git")
+       return parts[len(parts)-1]
 }
 
 // extractBaseURL extracts the base URL from a full repository URL. The base
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/credentials/github/app_test.go 
new/kargo-cli-1.9.3/pkg/credentials/github/app_test.go
--- old/kargo-cli-1.9.2/pkg/credentials/github/app_test.go      2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/credentials/github/app_test.go      2026-02-17 
18:59:04.000000000 +0100
@@ -178,17 +178,6 @@
                        },
                        expected: true,
                },
-               {
-                       name:     "valid with .git suffix",
-                       credType: credentials.TypeGit,
-                       repoURL:  testRepoURL + ".git",
-                       getDataMap: func() map[string][]byte {
-                               dm := maps.Clone(supportedDataMap)
-                               delete(dm, clientIDKey)
-                               return dm
-                       },
-                       expected: true,
-               },
        }
        for _, testCase := range testCases {
                t.Run(testCase.name, func(t *testing.T) {
@@ -539,11 +528,6 @@
                        expected: "repo",
                },
                {
-                       name:     "GitHub URL with .git suffix",
-                       repoURL:  "https://github.com/example/repo.git";,
-                       expected: "repo",
-               },
-               {
                        name:     "GitHub Enterprise URL",
                        repoURL:  "https://github.example.com/example/repo";,
                        expected: "repo",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/approve_freight_v1alpha1.go 
new/kargo-cli-1.9.3/pkg/server/approve_freight_v1alpha1.go
--- old/kargo-cli-1.9.2/pkg/server/approve_freight_v1alpha1.go  2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/approve_freight_v1alpha1.go  2026-02-17 
18:59:04.000000000 +0100
@@ -9,7 +9,6 @@
 
        "connectrpc.com/connect"
        "github.com/gin-gonic/gin"
-       "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/types"
        "sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -92,11 +91,7 @@
        if err := s.authorizeFn(
                ctx,
                "promote",
-               schema.GroupVersionResource{
-                       Group:    kargoapi.GroupVersion.Group,
-                       Version:  kargoapi.GroupVersion.Version,
-                       Resource: "stages",
-               },
+               kargoapi.GroupVersion.WithResource("stages"),
                "",
                types.NamespacedName{
                        Namespace: project,
@@ -197,6 +192,20 @@
        ); err != nil {
                _ = c.Error(err)
                return
+       }
+
+       if err := s.authorizeFn(
+               ctx,
+               "promote",
+               kargoapi.GroupVersion.WithResource("stages"),
+               "",
+               types.NamespacedName{
+                       Namespace: project,
+                       Name:      stageName,
+               },
+       ); err != nil {
+               _ = c.Error(err)
+               return
        }
 
        if freight.IsApprovedFor(stageName) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/approve_freight_v1alpha1_test.go 
new/kargo-cli-1.9.3/pkg/server/approve_freight_v1alpha1_test.go
--- old/kargo-cli-1.9.2/pkg/server/approve_freight_v1alpha1_test.go     
2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/approve_freight_v1alpha1_test.go     
2026-02-17 18:59:04.000000000 +0100
@@ -10,6 +10,7 @@
        "connectrpc.com/connect"
        "github.com/stretchr/testify/require"
        corev1 "k8s.io/api/core/v1"
+       apierrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/types"
@@ -442,10 +443,45 @@
                                },
                        },
                        {
+                               name: "not authorized to approve (not 
authorized to promote)",
+                               clientBuilder: fake.NewClientBuilder().
+                                       WithObjects(testProject, testFreight, 
testStage).
+                                       WithStatusSubresource(testFreight),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return apierrors.NewForbidden(
+                                                       
kargoapi.GroupVersion.WithResource("stages").GroupResource(),
+                                                       testStageName,
+                                                       errors.New("not 
authorized"),
+                                               )
+                                       }
+                               },
+                               assertions: func(t *testing.T, w 
*httptest.ResponseRecorder, _ client.Client) {
+                                       require.Equal(t, http.StatusForbidden, 
w.Code)
+                               },
+                       },
+                       {
                                name: "approves Freight",
                                clientBuilder: fake.NewClientBuilder().
                                        WithObjects(testProject, testFreight, 
testStage).
                                        WithStatusSubresource(testFreight),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return nil
+                                       }
+                               },
                                assertions: func(t *testing.T, w 
*httptest.ResponseRecorder, c client.Client) {
                                        require.Equal(t, http.StatusOK, w.Code)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/create_or_update_resource_v1alpha1.go 
new/kargo-cli-1.9.3/pkg/server/create_or_update_resource_v1alpha1.go
--- old/kargo-cli-1.9.2/pkg/server/create_or_update_resource_v1alpha1.go        
2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/create_or_update_resource_v1alpha1.go        
2026-02-17 18:59:04.000000000 +0100
@@ -56,7 +56,7 @@
                }
                // If we just created a Project successfully, keep track of 
this Project
                // being one that was created in the course of this API call.
-               if result.CreatedResourceManifest != nil && 
resource.GroupVersionKind() == projectGVK {
+               if err == nil && result.CreatedResourceManifest != nil && 
resource.GroupVersionKind() == projectGVK {
                        createdProjects[resource.GetName()] = struct{}{}
                }
                // Convert to protobuf result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/create_resource_v1alpha1.go 
new/kargo-cli-1.9.3/pkg/server/create_resource_v1alpha1.go
--- old/kargo-cli-1.9.2/pkg/server/create_resource_v1alpha1.go  2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/create_resource_v1alpha1.go  2026-02-17 
18:59:04.000000000 +0100
@@ -64,7 +64,7 @@
                }
                // If we just created a Project successfully, keep track of 
this Project
                // being one that was created in the course of this API call.
-               if resource.GroupVersionKind() == projectGVK {
+               if err == nil && resource.GroupVersionKind() == projectGVK {
                        createdProjects[resource.GetName()] = struct{}{}
                }
                // Convert to protobuf result
@@ -158,7 +158,7 @@
                }
                // If we just created a Project successfully, keep track of 
this Project
                // being one that was created in the course of this API call.
-               if resource.GroupVersionKind() == projectGVK {
+               if err == nil && resource.GroupVersionKind() == projectGVK {
                        createdProjects[resource.GetName()] = struct{}{}
                }
                results = append(results, result)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/server/project_middleware.go 
new/kargo-cli-1.9.3/pkg/server/project_middleware.go
--- old/kargo-cli-1.9.2/pkg/server/project_middleware.go        2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/project_middleware.go        2026-02-17 
18:59:04.000000000 +0100
@@ -18,7 +18,11 @@
                        return
                }
                p := &kargoapi.Project{}
-               if err := s.client.Get(
+               var cl client.Client = s.client
+               if s.client != nil && s.client.InternalClient() != nil {
+                       cl = s.client.InternalClient()
+               }
+               if err := cl.Get(
                        c.Request.Context(),
                        client.ObjectKey{Name: project},
                        p,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/promote_downstream_v1alpha1.go 
new/kargo-cli-1.9.3/pkg/server/promote_downstream_v1alpha1.go
--- old/kargo-cli-1.9.2/pkg/server/promote_downstream_v1alpha1.go       
2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/promote_downstream_v1alpha1.go       
2026-02-17 18:59:04.000000000 +0100
@@ -300,6 +300,22 @@
                return
        }
 
+       for _, downstream := range downstreams {
+               if err := s.authorizeFn(
+                       ctx,
+                       "promote",
+                       kargoapi.GroupVersion.WithResource("stages"),
+                       "",
+                       types.NamespacedName{
+                               Namespace: downstream.Namespace,
+                               Name:      downstream.Name,
+                       },
+               ); err != nil {
+                       _ = c.Error(err)
+                       return
+               }
+       }
+
        // Validate that freight is available to all downstream stages
        for _, downstream := range downstreams {
                if !downstream.IsFreightAvailable(freight) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/promote_downstream_v1alpha1_test.go 
new/kargo-cli-1.9.3/pkg/server/promote_downstream_v1alpha1_test.go
--- old/kargo-cli-1.9.2/pkg/server/promote_downstream_v1alpha1_test.go  
2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/promote_downstream_v1alpha1_test.go  
2026-02-17 18:59:04.000000000 +0100
@@ -10,6 +10,7 @@
        "connectrpc.com/connect"
        "github.com/stretchr/testify/require"
        corev1 "k8s.io/api/core/v1"
+       apierrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/types"
@@ -791,13 +792,54 @@
                                },
                        },
                        {
-                               name: "Successfully promote downstream",
+                               name: "not authorized to promote to a 
downstream stage",
                                clientBuilder: 
fake.NewClientBuilder().WithObjects(
                                        testProject,
                                        testStage,
                                        testDownstreamStage,
                                        testFreight,
                                ),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return apierrors.NewForbidden(
+                                                       
kargoapi.GroupVersion.WithResource("stages").GroupResource(),
+                                                       
testDownstreamStage.Name,
+                                                       errors.New("not 
authorized"),
+                                               )
+                                       }
+                               },
+                               body: mustJSONBody(promoteDownstreamRequest{
+                                       Freight: testFreight.Name,
+                               }),
+                               assertions: func(t *testing.T, w 
*httptest.ResponseRecorder, _ client.Client) {
+                                       require.Equal(t, http.StatusForbidden, 
w.Code)
+                               },
+                       },
+                       {
+                               name: "successfully promotes downstream",
+                               clientBuilder: 
fake.NewClientBuilder().WithObjects(
+                                       testProject,
+                                       testStage,
+                                       testDownstreamStage,
+                                       testFreight,
+                               ),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return nil
+                                       }
+                               },
                                body: mustJSONBody(promoteDownstreamRequest{
                                        Freight: testFreight.Name,
                                }),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/promote_to_stage_v1alpha1.go 
new/kargo-cli-1.9.3/pkg/server/promote_to_stage_v1alpha1.go
--- old/kargo-cli-1.9.2/pkg/server/promote_to_stage_v1alpha1.go 2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/promote_to_stage_v1alpha1.go 2026-02-17 
18:59:04.000000000 +0100
@@ -9,7 +9,6 @@
        "connectrpc.com/connect"
        "github.com/gin-gonic/gin"
        apierrors "k8s.io/apimachinery/pkg/api/errors"
-       "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/types"
        "sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -94,26 +93,10 @@
                return nil, connect.NewError(connect.CodeNotFound, err)
        }
 
-       if !s.isFreightAvailableFn(stage, freight) {
-               // nolint:staticcheck
-               return nil, connect.NewError(
-                       connect.CodeInvalidArgument,
-                       fmt.Errorf(
-                               "Freight %q is not available to Stage %q",
-                               freightName,
-                               stageName,
-                       ),
-               )
-       }
-
        if err = s.authorizeFn(
                ctx,
                "promote",
-               schema.GroupVersionResource{
-                       Group:    kargoapi.GroupVersion.Group,
-                       Version:  kargoapi.GroupVersion.Version,
-                       Resource: "stages",
-               },
+               kargoapi.GroupVersion.WithResource("stages"),
                "",
                types.NamespacedName{
                        Namespace: project,
@@ -123,6 +106,18 @@
                return nil, err
        }
 
+       if !s.isFreightAvailableFn(stage, freight) {
+               // nolint:staticcheck
+               return nil, connect.NewError(
+                       connect.CodeInvalidArgument,
+                       fmt.Errorf(
+                               "Freight %q is not available to Stage %q",
+                               freightName,
+                               stageName,
+                       ),
+               )
+       }
+
        promotion, err := kargo.NewPromotionBuilder(s.client).Build(ctx, 
*stage, freight.Name)
        if err != nil {
                return nil, fmt.Errorf("build promotion: %w", err)
@@ -250,6 +245,20 @@
                freight = &list.Items[0]
        }
 
+       if err := s.authorizeFn(
+               ctx,
+               "promote",
+               kargoapi.GroupVersion.WithResource("stages"),
+               "",
+               types.NamespacedName{
+                       Namespace: project,
+                       Name:      stageName,
+               },
+       ); err != nil {
+               _ = c.Error(err)
+               return
+       }
+
        // Validate that the Freight is available to the Stage
        if !stage.IsFreightAvailable(freight) {
                _ = c.Error(libhttp.ErrorStr(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/promote_to_stage_v1alpha1_test.go 
new/kargo-cli-1.9.3/pkg/server/promote_to_stage_v1alpha1_test.go
--- old/kargo-cli-1.9.2/pkg/server/promote_to_stage_v1alpha1_test.go    
2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/promote_to_stage_v1alpha1_test.go    
2026-02-17 18:59:04.000000000 +0100
@@ -10,6 +10,7 @@
        "connectrpc.com/connect"
        "github.com/stretchr/testify/require"
        corev1 "k8s.io/api/core/v1"
+       apierrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/types"
@@ -229,7 +230,7 @@
                        },
                },
                {
-                       name: "Freight not available",
+                       name: "promoting not authorized",
                        req: &svcv1alpha1.PromoteToStageRequest{
                                Project: "fake-project",
                                Stage:   "fake-stage",
@@ -251,12 +252,23 @@
                                getFreightByNameOrAliasFn: func(
                                        context.Context,
                                        client.Client,
-                                       string, string, string,
+                                       string,
+                                       string,
+                                       string,
                                ) (*kargoapi.Freight, error) {
                                        return &kargoapi.Freight{}, nil
                                },
                                isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
-                                       return false
+                                       return true
+                               },
+                               authorizeFn: func(
+                                       context.Context,
+                                       string,
+                                       schema.GroupVersionResource,
+                                       string,
+                                       client.ObjectKey,
+                               ) error {
+                                       return errors.New("not authorized")
                                },
                        },
                        assertions: func(
@@ -265,16 +277,11 @@
                                _ 
*connect.Response[svcv1alpha1.PromoteToStageResponse],
                                err error,
                        ) {
-                               require.Error(t, err)
-                               var connErr *connect.Error
-                               require.True(t, errors.As(err, &connErr))
-                               require.Equal(t, connect.CodeInvalidArgument, 
connErr.Code())
-                               require.Contains(t, connErr.Message(), 
"Freight")
-                               require.Contains(t, connErr.Message(), "is not 
available to Stage")
+                               require.Error(t, err, "not authorized")
                        },
                },
                {
-                       name: "promoting not authorized",
+                       name: "Freight not available",
                        req: &svcv1alpha1.PromoteToStageRequest{
                                Project: "fake-project",
                                Stage:   "fake-stage",
@@ -296,15 +303,10 @@
                                getFreightByNameOrAliasFn: func(
                                        context.Context,
                                        client.Client,
-                                       string,
-                                       string,
-                                       string,
+                                       string, string, string,
                                ) (*kargoapi.Freight, error) {
                                        return &kargoapi.Freight{}, nil
                                },
-                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
-                                       return true
-                               },
                                authorizeFn: func(
                                        context.Context,
                                        string,
@@ -312,7 +314,10 @@
                                        string,
                                        client.ObjectKey,
                                ) error {
-                                       return errors.New("not authorized")
+                                       return nil
+                               },
+                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
+                                       return false
                                },
                        },
                        assertions: func(
@@ -321,7 +326,12 @@
                                _ 
*connect.Response[svcv1alpha1.PromoteToStageResponse],
                                err error,
                        ) {
-                               require.Error(t, err, "not authorized")
+                               require.Error(t, err)
+                               var connErr *connect.Error
+                               require.True(t, errors.As(err, &connErr))
+                               require.Equal(t, connect.CodeInvalidArgument, 
connErr.Code())
+                               require.Contains(t, connErr.Message(), 
"Freight")
+                               require.Contains(t, connErr.Message(), "is not 
available to Stage")
                        },
                },
                {
@@ -355,9 +365,6 @@
                                ) (*kargoapi.Freight, error) {
                                        return &kargoapi.Freight{}, nil
                                },
-                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
-                                       return true
-                               },
                                authorizeFn: func(
                                        context.Context,
                                        string,
@@ -367,6 +374,9 @@
                                ) error {
                                        return nil
                                },
+                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
+                                       return true
+                               },
                        },
                        assertions: func(
                                t *testing.T,
@@ -413,9 +423,6 @@
                                                },
                                        }, nil
                                },
-                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
-                                       return true
-                               },
                                authorizeFn: func(
                                        context.Context,
                                        string,
@@ -425,6 +432,9 @@
                                ) error {
                                        return nil
                                },
+                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
+                                       return true
+                               },
                                createPromotionFn: func(
                                        context.Context,
                                        client.Object,
@@ -478,9 +488,6 @@
                                                },
                                        }, nil
                                },
-                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
-                                       return true
-                               },
                                authorizeFn: func(
                                        context.Context,
                                        string,
@@ -490,6 +497,9 @@
                                ) error {
                                        return nil
                                },
+                               isFreightAvailableFn: func(*kargoapi.Stage, 
*kargoapi.Freight) bool {
+                                       return true
+                               },
                                createPromotionFn: func(
                                        context.Context,
                                        client.Object,
@@ -641,8 +651,75 @@
                                },
                        },
                        {
+                               name:          "promoting not authorized",
+                               clientBuilder: 
fake.NewClientBuilder().WithObjects(testProject, testStage, testFreight),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return apierrors.NewForbidden(
+                                                       
kargoapi.GroupVersion.WithResource("stages").GroupResource(),
+                                                       testStage.Name,
+                                                       errors.New("not 
authorized"),
+                                               )
+                                       }
+                               },
+                               body: mustJSONBody(promoteToStageRequest{
+                                       Freight: testFreight.Name,
+                               }),
+                               assertions: func(t *testing.T, w 
*httptest.ResponseRecorder, _ client.Client) {
+                                       require.Equal(t, http.StatusForbidden, 
w.Code)
+                               },
+                       },
+                       {
+                               name: "Freight not available to Stage",
+                               clientBuilder: 
fake.NewClientBuilder().WithObjects(
+                                       testProject,
+                                       func() *kargoapi.Stage {
+                                               s := testStage.DeepCopy()
+                                               
s.Spec.RequestedFreight[0].Sources = kargoapi.FreightSources{
+                                                       Stages: 
[]string{"some-other-stage"},
+                                               }
+                                               return s
+                                       }(),
+                                       testFreight,
+                               ),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return nil
+                                       }
+                               },
+                               body: mustJSONBody(promoteToStageRequest{
+                                       Freight: testFreight.Name,
+                               }),
+                               assertions: func(t *testing.T, w 
*httptest.ResponseRecorder, _ client.Client) {
+                                       require.Equal(t, http.StatusBadRequest, 
w.Code)
+                               },
+                       },
+                       {
                                name:          "Successfully promote by freight 
name",
                                clientBuilder: 
fake.NewClientBuilder().WithObjects(testProject, testStage, testFreight),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return nil
+                                       }
+                               },
                                body: mustJSONBody(promoteToStageRequest{
                                        Freight: testFreight.Name,
                                }),
@@ -661,6 +738,17 @@
                        {
                                name:          "Successfully promote by freight 
alias",
                                clientBuilder: 
fake.NewClientBuilder().WithObjects(testProject, testStage, testFreight),
+                               serverSetup: func(_ *testing.T, s *server) {
+                                       s.authorizeFn = func(
+                                               context.Context,
+                                               string,
+                                               schema.GroupVersionResource,
+                                               string,
+                                               client.ObjectKey,
+                                       ) error {
+                                               return nil
+                                       }
+                               },
                                body: mustJSONBody(promoteToStageRequest{
                                        FreightAlias: "fake-alias",
                                }),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/server/rest_test.go 
new/kargo-cli-1.9.3/pkg/server/rest_test.go
--- old/kargo-cli-1.9.2/pkg/server/rest_test.go 2026-02-02 13:44:17.000000000 
+0100
+++ new/kargo-cli-1.9.3/pkg/server/rest_test.go 2026-02-17 18:59:04.000000000 
+0100
@@ -41,7 +41,10 @@
        headers       map[string]string
        clientBuilder *fake.ClientBuilder
        serverConfig  *config.ServerConfig
-       assertions    func(*testing.T, *httptest.ResponseRecorder, 
client.Client)
+       // serverSetup is an optional function that can be used to perform 
additional
+       // case-specific server initialization.
+       serverSetup func(*testing.T, *server)
+       assertions  func(*testing.T, *httptest.ResponseRecorder, client.Client)
 }
 
 func testRESTEndpoint(
@@ -108,6 +111,10 @@
                                rbac.RolesDatabaseConfig{KargoNamespace: 
testKargoNamespace},
                        )
 
+                       if testCase.serverSetup != nil {
+                               testCase.serverSetup(t, s)
+                       }
+
                        u := url
                        if testCase.url != "" {
                                u = testCase.url
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/pkg/server/update_resource_v1alpha1.go 
new/kargo-cli-1.9.3/pkg/server/update_resource_v1alpha1.go
--- old/kargo-cli-1.9.2/pkg/server/update_resource_v1alpha1.go  2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/server/update_resource_v1alpha1.go  2026-02-17 
18:59:04.000000000 +0100
@@ -133,7 +133,7 @@
                }
                // If we just created a Project successfully, keep track of 
this Project
                // being one that was created in the course of this API call.
-               if result.CreatedResourceManifest != nil && 
resource.GroupVersionKind() == projectGVK {
+               if err == nil && result.CreatedResourceManifest != nil && 
resource.GroupVersionKind() == projectGVK {
                        createdProjects[resource.GetName()] = struct{}{}
                }
                results = append(results, result)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/subscription/schemas/chart.json 
new/kargo-cli-1.9.3/pkg/subscription/schemas/chart.json
--- old/kargo-cli-1.9.2/pkg/subscription/schemas/chart.json     2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/subscription/schemas/chart.json     2026-02-17 
18:59:04.000000000 +0100
@@ -8,7 +8,7 @@
         "repoURL": {
             "type": "string",
             "minLength": 1,
-            "pattern": 
"^(((https?)|(oci))://)(([\\w\\d\\.\\-]+)(:([\\d]+)?)?(/.*)*$",
+            "pattern": 
"^(((https?)|(oci))://)(([\\w\\d\\.\\-]+)(:([\\d]+)?)?(/.*)*)$",
             "description": "RepoURL specifies the URL of a Helm chart 
repository. It may be a classic chart repository (using HTTP/S) OR a repository 
within an OCI registry. Classic chart repositories can contain differently 
named charts. When this field points to such a repository, the name field MUST 
also be used to specify the name of the desired chart within that repository. 
In the case of a repository within an OCI registry, the URL implicitly points 
to a specific chart and the name field MUST NOT be used. This field is 
required."
         },
         "name": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/urls/common.go 
new/kargo-cli-1.9.3/pkg/urls/common.go
--- old/kargo-cli-1.9.2/pkg/urls/common.go      1970-01-01 01:00:00.000000000 
+0100
+++ new/kargo-cli-1.9.3/pkg/urls/common.go      2026-02-17 18:59:04.000000000 
+0100
@@ -0,0 +1,20 @@
+package urls
+
+import (
+       "strings"
+       "unicode"
+)
+
+// SanitizeURL removes leading and trailing whitespace only from a string
+// presumed to represent a URL. It additionally removes non-printable runes 
such
+// as byte order marks (BOMs) from anywhere in a string. Leading whitespace and
+// non-printable runes can easily be copied and pasted without a user realizing
+// and are known to interfere with URL parsing.
+func SanitizeURL(url string) string {
+       return strings.TrimSpace(strings.Map(func(r rune) rune {
+               if unicode.IsPrint(r) {
+                       return r
+               }
+               return -1
+       }, url))
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/urls/common_test.go 
new/kargo-cli-1.9.3/pkg/urls/common_test.go
--- old/kargo-cli-1.9.2/pkg/urls/common_test.go 1970-01-01 01:00:00.000000000 
+0100
+++ new/kargo-cli-1.9.3/pkg/urls/common_test.go 2026-02-17 18:59:04.000000000 
+0100
@@ -0,0 +1,38 @@
+package urls
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/require"
+)
+
+func TestSanitizeURL(t *testing.T) {
+       testCases := map[string]string{
+               // Leading and trailing whitespace
+               "  https://example.com/repo  ":     "https://example.com/repo";,
+               "\n\thttps://example.com/repo\t\n": "https://example.com/repo";,
+               // Non-printable runes (e.g., BOMs)
+               "\uFEFFhttps://example.com/repo":               
"https://example.com/repo";,
+               "https://example.com/\u200Brepo":               
"https://example.com/repo";,
+               "https://example.com/repo\uFEFF":               
"https://example.com/repo";,
+               "\uFEFF https://example.com/\u200Brepo \uFEFF": 
"https://example.com/repo";,
+               // Combination of both
+               "\uFEFF \n https://example.com/\trepo \u200B \n ": 
"https://example.com/repo";,
+               // No changes needed
+               "https://example.com/repo":   "https://example.com/repo";,
+               "ftp://example.com/resource": "ftp://example.com/resource";,
+               "   ":                        "",
+               "":                           "",
+               // Internal whitespace should not be removed
+               "https://example.com/ myrepo": "https://example.com/ myrepo",
+               // Internal non-printable runes should be removed
+               "https://example.com/\u200Bmyrepo": 
"https://example.com/myrepo";,
+       }
+       for in, out := range testCases {
+               t.Run(in, func(t *testing.T) {
+                       require.Equal(t, out,
+                               SanitizeURL(in),
+                       )
+               })
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/urls/git.go 
new/kargo-cli-1.9.3/pkg/urls/git.go
--- old/kargo-cli-1.9.2/pkg/urls/git.go 2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/pkg/urls/git.go 2026-02-17 18:59:04.000000000 +0100
@@ -20,7 +20,7 @@
 // normalized will be returned as-is.
 func NormalizeGit(repo string) string {
        origRepo := repo
-       repo = strings.ToLower(repo)
+       repo = SanitizeURL(strings.ToLower(repo))
 
        // HTTP/S URLs
        if strings.HasPrefix(repo, "http://";) || strings.HasPrefix(repo, 
"https://";) {
@@ -53,7 +53,6 @@
                repoURL.Path = strings.TrimSuffix(repoURL.Path, ".git")
                return repoURL.String()
        }
-
        // URLS of the form [user@]host.xz[:path/to/repo[.git][/]]
        matches := scpSyntaxRegex.FindStringSubmatch(repo)
        if len(matches) != 2 && len(matches) != 3 {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/urls/helm.go 
new/kargo-cli-1.9.3/pkg/urls/helm.go
--- old/kargo-cli-1.9.2/pkg/urls/helm.go        2026-02-02 13:44:17.000000000 
+0100
+++ new/kargo-cli-1.9.3/pkg/urls/helm.go        2026-02-17 18:59:04.000000000 
+0100
@@ -1,6 +1,7 @@
 package urls
 
 import (
+       "net/url"
        "strings"
 )
 
@@ -8,14 +9,18 @@
 // Crucially, this function removes the oci:// prefix from the URL if there is
 // one.
 func NormalizeChart(repo string) string {
+       ogRepo := repo
+       repo = SanitizeURL(repo)
+       // just to check validity
+       if _, err := url.Parse(repo); err != nil {
+               return ogRepo
+       }
        // Note: We lean a bit on image.NormalizeURL() because it is excellent 
at
        // normalizing the many different forms of equivalent URLs for Docker 
Hub
        // repositories.
        return NormalizeImage(
                strings.TrimPrefix(
-                       strings.ToLower(
-                               strings.TrimSpace(repo),
-                       ),
+                       strings.ToLower(repo),
                        "oci://",
                ),
        )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/pkg/urls/image.go 
new/kargo-cli-1.9.3/pkg/urls/image.go
--- old/kargo-cli-1.9.2/pkg/urls/image.go       2026-02-02 13:44:17.000000000 
+0100
+++ new/kargo-cli-1.9.3/pkg/urls/image.go       2026-02-17 18:59:04.000000000 
+0100
@@ -16,9 +16,11 @@
 // canonical representation of a repository URL is needed. Any URL that cannot
 // be normalized will be returned as-is.
 func NormalizeImage(repoURL string) string {
+       ogRepoURL := repoURL
+       repoURL = SanitizeURL(strings.ToLower(repoURL))
        parsed, err := name.ParseReference(repoURL, name.WeakValidation)
        if err != nil {
-               return repoURL
+               return ogRepoURL
        }
        reg := parsed.Context().Registry.Name()
        repo := parsed.Context().RepositoryStr()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.9.2/ui/src/features/project/pipelines/promotion/promote.tsx 
new/kargo-cli-1.9.3/ui/src/features/project/pipelines/promotion/promote.tsx
--- old/kargo-cli-1.9.2/ui/src/features/project/pipelines/promotion/promote.tsx 
2026-02-02 13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/ui/src/features/project/pipelines/promotion/promote.tsx 
2026-02-17 18:59:04.000000000 +0100
@@ -8,13 +8,14 @@
 import { paths } from '@ui/config/paths';
 import { useExtensionsContext } from '@ui/extensions/extensions-context';
 import { ModalComponentProps } from '@ui/features/common/modal/modal-context';
-import { useActionContext } from 
'@ui/features/project/pipelines/context/action-context';
+import { IAction, useActionContext } from 
'@ui/features/project/pipelines/context/action-context';
 import {
   promoteDownstream,
   promoteToStage
 } from '@ui/gen/api/service/v1alpha1/service-KargoService_connectquery';
 import { Freight, Stage } from '@ui/gen/api/v1alpha1/generated_pb';
 
+import { useDictionaryContext } from '../context/dictionary-context';
 import { isStageControlFlow } from '../nodes/stage-meta-utils';
 
 import { FreightDetails } from './freight-details';
@@ -30,7 +31,10 @@
   const navigate = useNavigate();
   const { promoteTabs } = useExtensionsContext();
 
-  const isControlFlow = isStageControlFlow(props.stage);
+  const dictionaryContext = useDictionaryContext();
+
+  const isDownstreamPromotion =
+    actionContext?.action?.type === IAction.PROMOTE_DOWNSTREAM || 
isStageControlFlow(props.stage);
 
   const freightAlias = props.freight?.alias;
   const stageName = props.stage?.metadata?.name;
@@ -70,7 +74,7 @@
       freight: props.freight?.metadata?.name
     };
 
-    if (isControlFlow) {
+    if (isDownstreamPromotion) {
       promoteDownstreamActionMutation.mutate(payload);
       return;
     }
@@ -78,13 +82,19 @@
     promoteActionMutation.mutate(payload);
   };
 
+  let promotingTo = stageName || '';
+
+  if (isDownstreamPromotion) {
+    promotingTo = [...(dictionaryContext?.subscribersByStage?.[promotingTo] || 
[])].join(', ');
+  }
+
   return (
     <Drawer
       open={props.visible}
       onClose={props.hide}
       title={
         <Flex align='center'>
-          Promote {freightAlias} to {stageName}
+          Promote {freightAlias} to {promotingTo}
         </Flex>
       }
       size='large'
@@ -97,7 +107,7 @@
           onClick={onPromote}
           loading={promoteActionMutation.isPending || 
promoteDownstreamActionMutation.isPending}
         >
-          Promote{isControlFlow && ' to downstream'}
+          Promote{isDownstreamPromotion && ' to downstream'}
         </Button>
       }
     >
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kargo-cli-1.9.2/ui/src/gen/subscriptions/chart.json 
new/kargo-cli-1.9.3/ui/src/gen/subscriptions/chart.json
--- old/kargo-cli-1.9.2/ui/src/gen/subscriptions/chart.json     2026-02-02 
13:44:17.000000000 +0100
+++ new/kargo-cli-1.9.3/ui/src/gen/subscriptions/chart.json     2026-02-17 
18:59:04.000000000 +0100
@@ -7,7 +7,7 @@
   "repoURL": {
    "type": "string",
    "minLength": 1,
-   "pattern": "^(((https?)|(oci))://)(([\\w\\d\\.\\-]+)(:([\\d]+)?)?(/.*)*$",
+   "pattern": "^(((https?)|(oci))://)(([\\w\\d\\.\\-]+)(:([\\d]+)?)?(/.*)*)$",
    "description": "RepoURL specifies the URL of a Helm chart repository. It 
may be a classic chart repository (using HTTP/S) OR a repository within an OCI 
registry. Classic chart repositories can contain differently named charts. When 
this field points to such a repository, the name field MUST also be used to 
specify the name of the desired chart within that repository. In the case of a 
repository within an OCI registry, the URL implicitly points to a specific 
chart and the name field MUST NOT be used. This field is required."
   },
   "name": {

++++++ kargo-cli.obsinfo ++++++
--- /var/tmp/diff_new_pack.Oo2knL/_old  2026-02-18 17:13:34.858085764 +0100
+++ /var/tmp/diff_new_pack.Oo2knL/_new  2026-02-18 17:13:34.862085930 +0100
@@ -1,5 +1,5 @@
 name: kargo-cli
-version: 1.9.2
-mtime: 1770036257
-commit: 141a6cd104f855d404119b78c5ae2646c66ea9e4
+version: 1.9.3
+mtime: 1771351144
+commit: 155c6852ffbffa2902f18e6c7add91a846e8d344
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/kargo-cli/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.kargo-cli.new.1977/vendor.tar.gz differ: char 93, 
line 2

Reply via email to