Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package updatecli for openSUSE:Factory 
checked in at 2026-08-28 19:51:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/updatecli (Old)
 and      /work/SRC/openSUSE:Factory/.updatecli.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "updatecli"

Fri Aug 28 19:51:49 2026 rev:45 rq:1374260 version:0.120.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/updatecli/updatecli.changes      2026-08-06 
16:27:38.683835969 +0200
+++ /work/SRC/openSUSE:Factory/.updatecli.new.1265/updatecli.changes    
2026-08-28 19:53:42.464242298 +0200
@@ -1,0 +2,21 @@
+Fri Aug 28 07:26:48 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.120.1:
+  * Bug Fixes
+    - fix: skip resource depends on skipped source (#9916)
+    - fix: go module cooldown with pseudo version (#9917)
+    - fix(yaml): pass keyonly condition with searchpattern when at
+      least one file matches (#9889)
+    - fix(temurin): include specificversion in ReportConfig (#9850)
+  * Maintenance
+    - deps: Bump Golang version to 1.26.6 (#9925)
+    - deps: bump Updatecli GH action to v3.5.0 (#9901)
+    - deps: bump Updatecli GH action to v0.120.0 (#9890)
+    - deps(updatecli/policies): bump all policies (#9877)
+    - deps(go): bump module go.opentelemetry.io/otel/trace to
+      v1.45.0 (#9861)
+    - chore(dockerfile): upgrade node version (#9822)
+  * Documentation
+    - Add checklist item for manual testing (#9905)
+
+-------------------------------------------------------------------

Old:
----
  updatecli-0.120.0.obscpio

New:
----
  updatecli-0.120.1.obscpio

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

Other differences:
------------------
++++++ updatecli.spec ++++++
--- /var/tmp/diff_new_pack.6axwXz/_old  2026-08-28 19:53:45.384344366 +0200
+++ /var/tmp/diff_new_pack.6axwXz/_new  2026-08-28 19:53:45.386344436 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           updatecli
-Version:        0.120.0
+Version:        0.120.1
 Release:        0
 Summary:        A Declarative Dependency Management tool
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.6axwXz/_old  2026-08-28 19:53:45.466347233 +0200
+++ /var/tmp/diff_new_pack.6axwXz/_new  2026-08-28 19:53:45.474347512 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/updatecli/updatecli.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v0.120.0</param>
+    <param name="revision">refs/tags/v0.120.1</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.6axwXz/_old  2026-08-28 19:53:45.515348946 +0200
+++ /var/tmp/diff_new_pack.6axwXz/_new  2026-08-28 19:53:45.521349155 +0200
@@ -3,6 +3,6 @@
                 <param 
name="url">https://github.com/updatecli/updatecli</param>
               <param 
name="changesrevision">786ba9fbb8374d9c23e18b062f29ac1a95fd2b3c</param></service><service
 name="tar_scm">
                 <param 
name="url">https://github.com/updatecli/updatecli.git</param>
-              <param 
name="changesrevision">6dcc92d2b1d89d81449865291a38cbe858b78905</param></service></servicedata>
+              <param 
name="changesrevision">5609a74b86406126f41f74e4d50f1481dbe81fd4</param></service></servicedata>
 (No newline at EOF)
 

++++++ updatecli-0.120.0.obscpio -> updatecli-0.120.1.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/updatecli-0.120.0/README.md 
new/updatecli-0.120.1/README.md
--- old/updatecli-0.120.0/README.md     2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/README.md     2026-08-13 21:59:35.000000000 +0200
@@ -122,7 +122,7 @@
 **Verify Container signature**
 
 ```
-cosign verify --certificate-identity-regexp 
"https://github.com/updatecli/updatecli"; --certificate-oidc-issuer 
"https://token.actions.githubusercontent.com"; 
ghcr.io/updatecli/updatecli:v0.119.0
+cosign verify --certificate-identity-regexp 
"https://github.com/updatecli/updatecli"; --certificate-oidc-issuer 
"https://token.actions.githubusercontent.com"; 
ghcr.io/updatecli/updatecli:v0.120.0
 ```
 
 ## Documentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/updatecli-0.120.0/go.mod new/updatecli-0.120.1/go.mod
--- old/updatecli-0.120.0/go.mod        2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/go.mod        2026-08-13 21:59:35.000000000 +0200
@@ -1,6 +1,6 @@
 module github.com/updatecli/updatecli
 
-go 1.26.5
+go 1.26.6
 
 retract v0.999.0 // Published accidentally.
 
@@ -75,12 +75,12 @@
        github.com/yuin/goldmark v1.8.5
        github.com/zclconf/go-cty v1.19.0
        go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
-       go.opentelemetry.io/otel v1.44.0
+       go.opentelemetry.io/otel v1.45.0
        go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
        go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0
        go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0
        go.opentelemetry.io/otel/sdk v1.44.0
-       go.opentelemetry.io/otel/trace v1.44.0
+       go.opentelemetry.io/otel/trace v1.45.0
        go.yaml.in/yaml/v3 v3.0.5
        go.yaml.in/yaml/v4 v4.0.0-rc.4
        golang.org/x/exp v0.0.0-20260603202125-055de637280b
@@ -219,7 +219,7 @@
        go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
        
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc 
v0.69.0 // indirect
        go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
-       go.opentelemetry.io/otel/metric v1.44.0 // indirect
+       go.opentelemetry.io/otel/metric v1.45.0 // indirect
        go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
        go.opentelemetry.io/proto/otlp v1.10.0 // indirect
        go.yaml.in/yaml/v2 v2.4.4 // indirect
@@ -267,7 +267,7 @@
        github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
        github.com/go-git/go-billy/v5 v5.9.0 // indirect
        github.com/go-gorp/gorp/v3 v3.1.0 // indirect
-       github.com/go-logr/logr v1.4.3 // indirect
+       github.com/go-logr/logr v1.4.4 // indirect
        github.com/go-openapi/jsonpointer v0.23.1 // indirect
        github.com/go-openapi/jsonreference v0.21.6 // indirect
        github.com/go-openapi/swag v0.26.1 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/updatecli-0.120.0/go.sum new/updatecli-0.120.1/go.sum
--- old/updatecli-0.120.0/go.sum        2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/go.sum        2026-08-13 21:59:35.000000000 +0200
@@ -334,8 +334,8 @@
 github.com/go-jose/go-jose/v4 v4.1.4 
h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
 github.com/go-jose/go-jose/v4 v4.1.4/go.mod 
h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
 github.com/go-logr/logr v1.2.2/go.mod 
h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
+github.com/go-logr/logr v1.4.4/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod 
h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@@ -816,8 +816,8 @@
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc 
v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 
h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod 
h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
-go.opentelemetry.io/otel v1.44.0 
h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
-go.opentelemetry.io/otel v1.44.0/go.mod 
h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
+go.opentelemetry.io/otel v1.45.0 
h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
+go.opentelemetry.io/otel v1.45.0/go.mod 
h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 
h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y=
 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod 
h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU=
 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 
h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
@@ -842,8 +842,8 @@
 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod 
h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0=
 go.opentelemetry.io/otel/log v0.19.0 
h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
 go.opentelemetry.io/otel/log v0.19.0/go.mod 
h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
-go.opentelemetry.io/otel/metric v1.44.0 
h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
-go.opentelemetry.io/otel/metric v1.44.0/go.mod 
h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
+go.opentelemetry.io/otel/metric v1.45.0 
h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
+go.opentelemetry.io/otel/metric v1.45.0/go.mod 
h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
 go.opentelemetry.io/otel/metric/x v0.66.0 
h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
 go.opentelemetry.io/otel/metric/x v0.66.0/go.mod 
h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
 go.opentelemetry.io/otel/sdk v1.44.0 
h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
@@ -852,8 +852,8 @@
 go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod 
h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
 go.opentelemetry.io/otel/sdk/metric v1.44.0 
h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
 go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod 
h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
-go.opentelemetry.io/otel/trace v1.44.0 
h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
-go.opentelemetry.io/otel/trace v1.44.0/go.mod 
h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
+go.opentelemetry.io/otel/trace v1.45.0 
h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
+go.opentelemetry.io/otel/trace v1.45.0/go.mod 
h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
 go.opentelemetry.io/proto/otlp v1.10.0 
h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
 go.opentelemetry.io/proto/otlp v1.10.0/go.mod 
h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/updatecli-0.120.0/pkg/core/pipeline/utils.go 
new/updatecli-0.120.1/pkg/core/pipeline/utils.go
--- old/updatecli-0.120.0/pkg/core/pipeline/utils.go    2026-08-05 
07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/core/pipeline/utils.go    2026-08-13 
21:59:35.000000000 +0200
@@ -51,6 +51,11 @@
                                if dependencyResult.Category == 
conditionCategory && dependencyResult.Result != result.SUCCESS {
                                        return true
                                }
+                               // A skipped source doesn't provide any value, 
so running its dependents
+                               // would have them consume an empty source 
input.
+                               if dependencyResult.Category == sourceCategory 
&& dependencyResult.Result == result.SKIPPED {
+                                       return true
+                               }
                                if dependencyResult.Result == result.FAILURE {
                                        // And operator but dep is failed
                                        return true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/updatecli-0.120.0/pkg/core/pipeline/utils_test.go 
new/updatecli-0.120.1/pkg/core/pipeline/utils_test.go
--- old/updatecli-0.120.0/pkg/core/pipeline/utils_test.go       2026-08-05 
07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/core/pipeline/utils_test.go       2026-08-13 
21:59:35.000000000 +0200
@@ -5,6 +5,7 @@
        "testing"
 
        "github.com/stretchr/testify/require"
+       "github.com/updatecli/updatecli/pkg/core/result"
 )
 
 func TestExtractDepsFromTemplate(t *testing.T) {
@@ -46,3 +47,93 @@
                })
        }
 }
+
+func TestShouldSkipResource(t *testing.T) {
+       testdata := []struct {
+               Name           string
+               Leaf           Node
+               DepsResults    map[string]*Node
+               ExpectedResult bool
+       }{
+               {
+                       Name: "target depending on a successful source runs",
+                       Leaf: Node{
+                               ID:        "target#mytarget",
+                               Category:  targetCategory,
+                               DependsOn: []Dependency{{ID: "source#mysource", 
Operator: andBooleanOperator}},
+                       },
+                       DepsResults: map[string]*Node{
+                               "source#mysource": {ID: "source#mysource", 
Category: sourceCategory, Result: result.SUCCESS},
+                       },
+                       ExpectedResult: false,
+               },
+               {
+                       // A skipped source has no value to provide, so its 
dependents would otherwise
+                       // consume an empty source input.
+                       Name: "target depending on a skipped source is skipped",
+                       Leaf: Node{
+                               ID:        "target#mytarget",
+                               Category:  targetCategory,
+                               DependsOn: []Dependency{{ID: "source#mysource", 
Operator: andBooleanOperator}},
+                       },
+                       DepsResults: map[string]*Node{
+                               "source#mysource": {ID: "source#mysource", 
Category: sourceCategory, Result: result.SKIPPED},
+                       },
+                       ExpectedResult: true,
+               },
+               {
+                       Name: "condition depending on a skipped source is 
skipped",
+                       Leaf: Node{
+                               ID:        "condition#mycondition",
+                               Category:  conditionCategory,
+                               DependsOn: []Dependency{{ID: "source#mysource", 
Operator: andBooleanOperator}},
+                       },
+                       DepsResults: map[string]*Node{
+                               "source#mysource": {ID: "source#mysource", 
Category: sourceCategory, Result: result.SKIPPED},
+                       },
+                       ExpectedResult: true,
+               },
+               {
+                       Name: "target depending on a failed source is skipped",
+                       Leaf: Node{
+                               ID:        "target#mytarget",
+                               Category:  targetCategory,
+                               DependsOn: []Dependency{{ID: "source#mysource", 
Operator: andBooleanOperator}},
+                       },
+                       DepsResults: map[string]*Node{
+                               "source#mysource": {ID: "source#mysource", 
Category: sourceCategory, Result: result.FAILURE},
+                       },
+                       ExpectedResult: true,
+               },
+               {
+                       // A skipped target only means it had nothing to 
change, which mustn't stop
+                       // the resources depending on it.
+                       Name: "target depending on a skipped target runs",
+                       Leaf: Node{
+                               ID:        "target#mytarget",
+                               Category:  targetCategory,
+                               DependsOn: []Dependency{{ID: 
"target#myothertarget", Operator: andBooleanOperator}},
+                       },
+                       DepsResults: map[string]*Node{
+                               "target#myothertarget": {ID: 
"target#myothertarget", Category: targetCategory, Result: result.SKIPPED},
+                       },
+                       ExpectedResult: false,
+               },
+               {
+                       Name: "resource without any dependency runs",
+                       Leaf: Node{
+                               ID:       "target#mytarget",
+                               Category: targetCategory,
+                       },
+                       ExpectedResult: false,
+               },
+       }
+
+       p := Pipeline{}
+
+       for _, tt := range testdata {
+               t.Run(tt.Name, func(t *testing.T) {
+                       require.Equal(t, tt.ExpectedResult, 
p.shouldSkipResource(&tt.Leaf, tt.DepsResults))
+               })
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/go/module/condition.go 
new/updatecli-0.120.1/pkg/plugins/resources/go/module/condition.go
--- old/updatecli-0.120.0/pkg/plugins/resources/go/module/condition.go  
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/go/module/condition.go  
2026-08-13 21:59:35.000000000 +0200
@@ -5,7 +5,6 @@
        "fmt"
        "os"
        "strings"
-       "time"
 
        "github.com/sirupsen/logrus"
        "github.com/updatecli/updatecli/pkg/core/pipeline/scm"
@@ -47,18 +46,8 @@
                                return true, fmt.Sprintf("version %q 
available", versionToCheck), nil
                        }
                case false:
-                       releaseDate, err := time.Parse(time.RFC3339, 
versionInfo.Time)
-                       if err != nil {
-                               logrus.Debugf("failed to parse release date for 
version %q from proxy %q: %v\n", versionToCheck, proxy, err)
-                               continue
-                       }
-                       if g.Spec.Age.Minimum != "" && 
g.Spec.Age.IsOlderThan(releaseDate, nil) {
-                               logrus.Debugf("ignoring version %q from proxy 
%q because its age is below %q (released on %s)\n", versionToCheck, proxy, 
g.Spec.Age.Minimum, releaseDate)
-                               continue
-                       }
-
-                       if g.Spec.Age.Maximum != "" && 
g.Spec.Age.IsNewerThan(releaseDate, nil) {
-                               logrus.Debugf("ignoring version %q from proxy 
%q because its age is above %q (released on %s)\n", versionToCheck, proxy, 
g.Spec.Age.Maximum, releaseDate)
+                       if !isVersionMatchingAge(versionInfo, g.Spec.Age) {
+                               logrus.Debugf("ignoring version %q from proxy 
%q because it doesn't match the age filter\n", versionToCheck, proxy)
                                continue
                        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/go/module/main.go 
new/updatecli-0.120.1/pkg/plugins/resources/go/module/main.go
--- old/updatecli-0.120.0/pkg/plugins/resources/go/module/main.go       
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/go/module/main.go       
2026-08-13 21:59:35.000000000 +0200
@@ -45,6 +45,17 @@
                newFilter.Pattern = "*"
        }
 
+       /*
+               Init must run after the fallback above, as it defaults an empty 
kind to "latest"
+               which would silently override the semantic versioning default 
of that resource.
+               It also provides the default pattern of a partially specified 
filter, such as
+               "latest" for a filter only setting `kind: latest`.
+       */
+       newFilter, err = newFilter.Init()
+       if err != nil {
+               return nil, err
+       }
+
        if err = newSpec.Age.Validate(); err != nil {
                return nil, fmt.Errorf("wrong age spec %v", err)
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/go/module/source.go 
new/updatecli-0.120.1/pkg/plugins/resources/go/module/source.go
--- old/updatecli-0.120.0/pkg/plugins/resources/go/module/source.go     
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/go/module/source.go     
2026-08-13 21:59:35.000000000 +0200
@@ -2,6 +2,7 @@
 
 import (
        "context"
+       "errors"
        "fmt"
 
        "github.com/updatecli/updatecli/pkg/core/result"
@@ -9,8 +10,18 @@
 
 // Source returns the latest go module version
 func (g *GoModule) Source(ctx context.Context, workingDir string, resultSource 
*result.Source) error {
-       version, _, err := g.versions(ctx)
+       version, err := g.versions(ctx)
        if err != nil {
+               /*
+                       Every published version is still cooling down, which is 
an expected state of
+                       the age filter rather than a failure, so the source is 
skipped instead.
+               */
+               if errors.Is(err, ErrNoVersionMatchingAge) {
+                       resultSource.Result = result.SKIPPED
+                       resultSource.Description = fmt.Sprintf("no version of 
the GO module %q matches the age filter yet", g.Spec.Module)
+                       return nil
+               }
+
                return fmt.Errorf("searching go module version: %w", err)
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/go/module/source_test.go 
new/updatecli-0.120.1/pkg/plugins/resources/go/module/source_test.go
--- old/updatecli-0.120.0/pkg/plugins/resources/go/module/source_test.go        
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/go/module/source_test.go        
2026-08-13 21:59:35.000000000 +0200
@@ -13,10 +13,11 @@
 
 func TestSource(t *testing.T) {
        tests := []struct {
-               name           string
-               spec           Spec
-               expectedResult string
-               expectedError  bool
+               name            string
+               spec            Spec
+               expectedResult  string
+               expectedError   bool
+               expectedSkipped bool
        }{
                {
                        spec: Spec{
@@ -84,7 +85,8 @@
                                        Minimum: "100y",
                                },
                        },
-                       expectedError: true,
+                       // Every published version is still cooling down, which 
is a skip and not a failure
+                       expectedSkipped: true,
                },
                {
                        spec: Spec{
@@ -110,7 +112,8 @@
                                        Maximum: "1s",
                                },
                        },
-                       expectedError: true,
+                       // Every published version is too old, which is a skip 
and not a failure
+                       expectedSkipped: true,
                },
        }
        for _, tt := range tests {
@@ -124,6 +127,12 @@
                                return
                        }
                        require.NoError(t, err)
+                       if tt.expectedSkipped {
+                               assert.Equal(t, result.SKIPPED, 
gotResult.Result)
+                               assert.Empty(t, gotResult.Information)
+                               return
+                       }
+                       assert.Equal(t, result.SUCCESS, gotResult.Result)
                        assert.Equal(t, tt.expectedResult, 
gotResult.Information)
                })
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/go/module/version.go 
new/updatecli-0.120.1/pkg/plugins/resources/go/module/version.go
--- old/updatecli-0.120.0/pkg/plugins/resources/go/module/version.go    
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/go/module/version.go    
2026-08-13 21:59:35.000000000 +0200
@@ -3,6 +3,7 @@
 import (
        "context"
        "encoding/json"
+       "errors"
        "fmt"
        "io"
        "net/http"
@@ -19,6 +20,11 @@
        "github.com/updatecli/updatecli/pkg/plugins/utils/version"
 )
 
+// ErrNoVersionMatchingAge is returned when a Golang module publishes versions 
but the age
+// filter discarded all of them. It reports a cooldown still running, not a 
lookup failure,
+// so callers are expected to skip rather than to fail.
+var ErrNoVersionMatchingAge = errors.New("no version matching the age filter")
+
 // versionInfo represents the structure of the version information returned by 
the Go proxy API.
 type versionInfo struct {
        Version string `json:"Version"`
@@ -26,7 +32,7 @@
 }
 
 // GetVersions fetch all versions of a Golang module
-func (g *GoModule) versions(ctx context.Context) (v string, versions []string, 
err error) {
+func (g *GoModule) versions(ctx context.Context) (v string, err error) {
 
        var GOPROXY string
        if g.Spec.Proxy != "" {
@@ -37,90 +43,139 @@
                GOPROXY = goModuleDefaultProxy
        }
 
+       // Tracks proxies which do publish versions for that module but none 
matching the age
+       // filter, so that a running cooldown isn't reported as a missing 
module.
+       heldBackByAge := false
+
        for _, proxy := range strings.Split(GOPROXY, ",") {
                proxy = strings.TrimSpace(proxy)
                if !isSupportedGoProxy(proxy) {
                        continue
                }
 
-               proxyVersions, err := getVersionsFromProxy(ctx, g.webClient, 
proxy, g.Spec.Module, g.Spec.Age)
+               publishedVersions, matchingVersions, err := 
getVersionsFromProxy(ctx, g.webClient, proxy, g.Spec.Module, g.Spec.Age)
                if err != nil {
                        logrus.Debugf("skipping proxy %q due to %v\n", proxy, 
err)
                        continue
                }
 
-               if proxyVersions == nil && 
isLatestVersionFilter(g.versionFilter) {
-                       pseudoVersion, err := getLatestVersionFromProxy(ctx, 
g.webClient, proxy, g.Spec.Module)
+               /*
+                       The module doesn't publish any version, so the only 
thing a Go proxy can report
+                       is the pseudo version of its latest commit.
+                       as explained on https://go.dev/ref/mod#goproxy-protocol
+               */
+               if len(publishedVersions) == 0 {
+                       if !isLatestVersionFilter(g.versionFilter) {
+                               logrus.Debugf("no version published for module 
%q on proxy %q\n", g.Spec.Module, proxy)
+                               continue
+                       }
+
+                       latestVersion, err := getLatestVersionFromProxy(ctx, 
g.webClient, proxy, g.Spec.Module)
                        if err != nil {
                                logrus.Debugf("skipping proxy %q due to %v\n", 
proxy, err)
                                continue
                        }
 
-                       if pseudoVersion != "" {
+                       if latestVersion.Version == "" {
+                               logrus.Debugf("no version published for module 
%q on proxy %q\n", g.Spec.Module, proxy)
+                               continue
+                       }
 
-                               if !isPseudoVersionMatchingAge(pseudoVersion, 
g.Spec.Age) {
-                                       logrus.Debugf("ignoring pseudo version 
%q from proxy %q because it doesn't match the age filter\n", pseudoVersion, 
proxy)
-                                       continue
-                               }
+                       if !isVersionMatchingAge(latestVersion, g.Spec.Age) {
+                               logrus.Debugf("ignoring version %q from proxy 
%q because it doesn't match the age filter\n", latestVersion.Version, proxy)
+                               heldBackByAge = true
+                               continue
+                       }
+
+                       logrus.Debugf("no version published for module %q on 
proxy %q, fallback to version %q\n", g.Spec.Module, proxy, 
latestVersion.Version)
 
-                               versions = append(versions, pseudoVersion)
+                       g.Version = version.Version{
+                               ParsedVersion:   latestVersion.Version,
+                               OriginalVersion: latestVersion.Version,
                        }
 
-                       logrus.Debugf("no version published for module %q on 
proxy %q, fallback to pseudo version %q\n", g.Spec.Module, proxy, pseudoVersion)
+                       return latestVersion.Version, nil
+               }
 
-                       return pseudoVersion, versions, nil
+               // The module publishes versions but the age filter discarded 
every one of them,
+               // which means the version we would have returned is still 
cooling down.
+               if len(matchingVersions) == 0 {
+                       logrus.Debugf("every version published for module %q on 
proxy %q is filtered out by the age filter\n", g.Spec.Module, proxy)
+                       heldBackByAge = true
+                       continue
                }
 
+               versions := versionNames(matchingVersions)
+
                /*
-                       The response should be a list of version separated by \n
-                       as explained on https://go.dev/ref/mod#goproxy-protocol
+                       A "latest" filter asks for the most recently published 
version, which the
+                       lexicographic sort below can't identify since "v1.9.0" 
sorts after "v1.10.0".
+                       Release dates are known whenever the age filter ran, so 
use them to fall back
+                       on the most recent version that isn't cooling down 
anymore.
                */
-               versions = append(versions, proxyVersions...)
+               if isNewestVersionFilter(g.versionFilter) && 
!g.Spec.Age.IsZero() {
+                       newestVersion := 
newestPublishedVersion(matchingVersions)
+                       if newestVersion != "" {
+                               g.Version = version.Version{
+                                       ParsedVersion:   newestVersion,
+                                       OriginalVersion: newestVersion,
+                               }
+
+                               return newestVersion, nil
+                       }
+               }
 
                sort.Strings(versions)
                g.Version, err = g.versionFilter.Search(versions)
                if err != nil {
-                       return "", nil, err
+                       return "", err
                }
 
-               return g.Version.GetVersion(), versions, nil
+               return g.Version.GetVersion(), nil
+
+       }
 
+       if heldBackByAge {
+               return "", fmt.Errorf("%w for GO module %q", 
ErrNoVersionMatchingAge, g.Spec.Module)
        }
 
-       return "", nil, fmt.Errorf("GO module %q not found on proxy %q", 
g.Spec.Module, GOPROXY)
+       return "", fmt.Errorf("GO module %q not found on proxy %q", 
g.Spec.Module, GOPROXY)
 }
 
-// getVersionsFromProxy returns all versions of a Golang module from a proxy
-func getVersionsFromProxy(ctx context.Context, client httpclient.HTTPClient, 
proxy, module string, releaseAge age.Spec) ([]string, error) {
+// getFromProxy queries a Go module proxy endpoint and returns its raw 
response body.
+// The endpoint is built by appending elems to the module path, as described on
+// https://go.dev/ref/mod#goproxy-protocol
+func getFromProxy(ctx context.Context, client httpclient.HTTPClient, proxy, 
module string, elems ...string) ([]byte, error) {
        URL, err := url.JoinPath(
                sanitizeGoProxy(proxy),
-               sanitizeGoModuleNameForProxy(module),
-               "@v", "list")
+               append([]string{sanitizeGoModuleNameForProxy(module)}, 
elems...)...)
        if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return nil, err
+               return nil, fmt.Errorf("building go module proxy url: %w", err)
        }
 
        // #nosec G704
        req, err := http.NewRequestWithContext(ctx, "GET", URL, nil)
        if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return nil, err
+               return nil, fmt.Errorf("building go module proxy request: %w", 
err)
        }
 
        res, err := client.Do(req)
        if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return nil, err
+               return nil, fmt.Errorf("querying go module proxy: %w", err)
        }
 
        defer res.Body.Close()
        if res.StatusCode >= 400 {
-               logrus.Errorf("something went wrong while getting golang module 
data: proxy %q returned HTTP %d (%s)\n", proxy, res.StatusCode, res.Status)
-               logrus.Debugf("skipping proxy %q due to HTTP %d (%s)\n", proxy, 
res.StatusCode, res.Status)
+               /*
+                       A proxy answering with an error status isn't 
necessarily a problem, GOPROXY may
+                       list several proxies and the next one is expected to 
serve the module, so the
+                       details are only reported at debug level.
+               */
+               logrus.Debugf("proxy %q returned HTTP %d (%s) for module %q\n", 
proxy, res.StatusCode, res.Status, module)
+
                body, err := httputil.DumpResponse(res, false)
                if err != nil {
-                       logrus.Debugf("failed to dump proxy response for %q: 
%q\n", proxy, err)
+                       logrus.Debugf("failed to dump proxy response for %q: 
%v\n", proxy, err)
                } else {
                        logrus.Debugf("\n%v\n", string(body))
                }
@@ -130,159 +185,85 @@
 
        data, err := io.ReadAll(res.Body)
        if err != nil {
-               logrus.Errorf("something went wrong while getting golang module 
api data%q\n", err)
-               return nil, err
-       }
-
-       // The response should be a list of version separated by \n
-       // as explained on https://go.dev/ref/mod#goproxy-protocol
-
-       dataStr := strings.TrimSpace(string(data))
-       versions := strings.Split(dataStr, "\n")
-
-       // Sanitize versions by filtering out versions that are too recent 
based on the MinimumReleaseAge filter
-       if !releaseAge.IsZero() {
-               sanitizedVersions := []string{}
-
-               for v := range versions {
-                       getVersionInfo, err := getVersionInfoFromProxy(ctx, 
client, proxy, module, versions[v])
-                       if err != nil {
-                               logrus.Debugf("ignoring version %q from proxy 
%q due to %q\n", versions[v], proxy, err)
-                               continue
-                       }
-
-                       releaseDate, err := time.Parse(time.RFC3339, 
getVersionInfo.Time)
-                       if err != nil {
-                               logrus.Debugf("ignoring version %q from proxy 
%q due to invalid release date format: %q\n", versions[v], proxy, err)
-                               continue
-                       }
-
-                       if releaseAge.Minimum != "" && 
releaseAge.IsOlderThan(releaseDate, nil) {
-                               logrus.Debugf("ignoring version %q from proxy 
%q because its age is below %q (released on %s)\n", versions[v], proxy, 
releaseAge.Minimum, releaseDate)
-                               continue
-                       }
-
-                       if releaseAge.Maximum != "" && 
releaseAge.IsNewerThan(releaseDate, nil) {
-                               logrus.Debugf("ignoring version %q from proxy 
%q because its age is above %q (released on %s)\n", versions[v], proxy, 
releaseAge.Maximum, releaseDate)
-                               continue
-                       }
-
-                       sanitizedVersions = append(sanitizedVersions, 
versions[v])
-               }
-               versions = sanitizedVersions
-       }
-
-       if len(versions) == 0 {
-               return nil, nil
+               return nil, fmt.Errorf("reading go module proxy response: %w", 
err)
        }
 
-       if len(versions) == 1 && versions[0] == "" {
-               return nil, nil
-       }
-
-       return versions, nil
+       return data, nil
 }
 
-// getLatestVersionFromProxy returns the latest version of a Golang module 
from a proxy
-func getLatestVersionFromProxy(ctx context.Context, client 
httpclient.HTTPClient, proxy, module string) (string, error) {
-       URL, err := url.JoinPath(
-               sanitizeGoProxy(proxy),
-               sanitizeGoModuleNameForProxy(module),
-               "@latest")
-
+// getVersionsFromProxy returns the versions of a Golang module published on a 
proxy.
+//
+// It returns both every published version and the subset matching the age 
filter, as
+// telling "this module doesn't publish any version" from "every published 
version is
+// still cooling down" requires very different handling from the caller.
+// The matching subset carries release dates because the goproxy protocol 
doesn't
+// guarantee any ordering of the published versions.
+func getVersionsFromProxy(ctx context.Context, client httpclient.HTTPClient, 
proxy, module string, releaseAge age.Spec) (publishedVersions []string, 
matchingVersions []versionInfo, err error) {
+       data, err := getFromProxy(ctx, client, proxy, module, "@v", "list")
        if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return "", err
+               return nil, nil, err
        }
 
-       // #nosec G704
-       req, err := http.NewRequestWithContext(ctx, "GET", URL, nil)
-       if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return "", err
+       // The response should be a list of version separated by \n
+       // as explained on https://go.dev/ref/mod#goproxy-protocol
+       dataStr := strings.TrimSpace(string(data))
+       if dataStr == "" {
+               return nil, nil, nil
        }
 
-       res, err := client.Do(req)
-       if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return "", err
-       }
+       publishedVersions = strings.Split(dataStr, "\n")
 
-       defer res.Body.Close()
-       if res.StatusCode >= 400 {
-               body, err := httputil.DumpResponse(res, false)
-               logrus.Errorf("something went wrong while getting golang module 
data %q\n", err)
-               logrus.Debugf("skipping proxy %q due to %q\n", proxy, err)
-               logrus.Debugf("\n%v\n", string(body))
+       // Without an age filter there is no reason to pay for one request per 
version.
+       if releaseAge.IsZero() {
+               matchingVersions = make([]versionInfo, 0, 
len(publishedVersions))
+               for _, v := range publishedVersions {
+                       matchingVersions = append(matchingVersions, 
versionInfo{Version: v})
+               }
 
-               return "", fmt.Errorf("GO module %q not found on proxy %q", 
module, proxy)
+               return publishedVersions, matchingVersions, nil
        }
 
-       data, err := io.ReadAll(res.Body)
-       if err != nil {
-               logrus.Errorf("something went wrong while getting npm api 
data%q\n", err)
-               return "", err
-       }
+       for _, v := range publishedVersions {
+               vInfo, err := getVersionInfoFromProxy(ctx, client, proxy, 
module, v)
+               if err != nil {
+                       logrus.Debugf("ignoring version %q from proxy %q due to 
%v\n", v, proxy, err)
+                       continue
+               }
 
-       type JSONData struct {
-               Version string `json:"Version"`
-               Time    string `json:"Time"`
-       }
+               if !isVersionMatchingAge(vInfo, releaseAge) {
+                       continue
+               }
 
-       jsonData := JSONData{}
-       err = json.Unmarshal(data, &jsonData)
-       if err != nil {
-               return "", fmt.Errorf("something went wrong while parsing go 
module api data %q", err)
+               matchingVersions = append(matchingVersions, vInfo)
        }
 
-       return jsonData.Version, nil
+       return publishedVersions, matchingVersions, nil
 }
 
-// getVersionInfoFromProxy returns the version information of a Golang module 
from a proxy
-func getVersionInfoFromProxy(ctx context.Context, client 
httpclient.HTTPClient, proxy, module, version string) (versionInfo, error) {
-       URL, err := url.JoinPath(
-               sanitizeGoProxy(proxy),
-               sanitizeGoModuleNameForProxy(module),
-               "@v",
-               version+".info")
-
-       if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return versionInfo{}, err
-       }
-
-       // #nosec G704
-       req, err := http.NewRequestWithContext(ctx, "GET", URL, nil)
+// getLatestVersionFromProxy returns the latest version of a Golang module 
from a proxy
+func getLatestVersionFromProxy(ctx context.Context, client 
httpclient.HTTPClient, proxy, module string) (versionInfo, error) {
+       data, err := getFromProxy(ctx, client, proxy, module, "@latest")
        if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
                return versionInfo{}, err
        }
 
-       res, err := client.Do(req)
-       if err != nil {
-               logrus.Errorf("something went wrong while getting go module api 
data %q\n", err)
-               return versionInfo{}, err
+       vInfo := versionInfo{}
+       if err = json.Unmarshal(data, &vInfo); err != nil {
+               return versionInfo{}, fmt.Errorf("something went wrong while 
parsing go module api data %q", err)
        }
 
-       defer res.Body.Close()
-       if res.StatusCode >= 400 {
-               body, err := httputil.DumpResponse(res, false)
-               logrus.Errorf("something went wrong while getting golang module 
data %q\n", err)
-               logrus.Debugf("skipping proxy %q due to %q\n", proxy, err)
-               logrus.Debugf("\n%v\n", string(body))
-
-               return versionInfo{}, fmt.Errorf("GO module %q not found on 
proxy %q", module, proxy)
-       }
+       return vInfo, nil
+}
 
-       data, err := io.ReadAll(res.Body)
+// getVersionInfoFromProxy returns the version information of a Golang module 
from a proxy
+func getVersionInfoFromProxy(ctx context.Context, client 
httpclient.HTTPClient, proxy, module, version string) (versionInfo, error) {
+       data, err := getFromProxy(ctx, client, proxy, module, "@v", 
version+".info")
        if err != nil {
-               logrus.Errorf("something went wrong while getting go module 
proxy response data%q\n", err)
                return versionInfo{}, err
        }
 
        vInfo := versionInfo{}
-       err = json.Unmarshal(data, &vInfo)
-       if err != nil {
+       if err = json.Unmarshal(data, &vInfo); err != nil {
                return versionInfo{}, fmt.Errorf("something went wrong while 
parsing go module api data %q", err)
        }
 
@@ -311,30 +292,66 @@
        return false
 }
 
-func isPseudoVersionMatchingAge(pseudoVersion string, releaseAge age.Spec) 
bool {
-       // Pseudo versions have the format vX.0.0-yyyymmddhhmmss-abcdefabcdef
-       parts := strings.Split(pseudoVersion, "-")
-       if len(parts) < 3 {
-               logrus.Debugf("invalid pseudo version format: %q\n", 
pseudoVersion)
-               return false
+// isNewestVersionFilter returns true if the version filter asks for the most 
recently
+// published version rather than for a version matching a pattern.
+func isNewestVersionFilter(versionfilter version.Filter) bool {
+       return versionfilter.Kind == version.LATESTVERSIONKIND &&
+               versionfilter.Pattern == version.LATESTVERSIONKIND
+}
+
+// isVersionMatchingAge returns true if a version falls inside the age window.
+// Versions without a parsable release date are never considered as matching.
+func isVersionMatchingAge(v versionInfo, releaseAge age.Spec) bool {
+       if releaseAge.IsZero() {
+               return true
        }
 
-       timestamp := parts[len(parts)-2]
-       releaseDate, err := time.Parse("20060102150405", timestamp)
+       releaseDate, err := time.Parse(time.RFC3339, v.Time)
        if err != nil {
-               logrus.Debugf("failed to parse release date from pseudo version 
%q: %q\n", pseudoVersion, err)
+               logrus.Debugf("ignoring version %q due to invalid release date 
%q: %v\n", v.Version, v.Time, err)
                return false
        }
 
        if releaseAge.Minimum != "" && releaseAge.IsOlderThan(releaseDate, nil) 
{
-               logrus.Debugf("ignoring pseudo version %q because its age is 
below %q (released on %s)\n", pseudoVersion, releaseAge.Minimum, releaseDate)
+               logrus.Debugf("ignoring version %q because its age is below %q 
(released on %s)\n", v.Version, releaseAge.Minimum, releaseDate)
                return false
        }
 
        if releaseAge.Maximum != "" && releaseAge.IsNewerThan(releaseDate, nil) 
{
-               logrus.Debugf("ignoring pseudo version %q because its age is 
above %q (released on %s)\n", pseudoVersion, releaseAge.Maximum, releaseDate)
+               logrus.Debugf("ignoring version %q because its age is above %q 
(released on %s)\n", v.Version, releaseAge.Maximum, releaseDate)
                return false
        }
 
        return true
 }
+
+// newestPublishedVersion returns the most recently published version among 
the provided
+// ones, or an empty string when none of them carries a parsable release date.
+func newestPublishedVersion(versions []versionInfo) string {
+       newestVersion := ""
+       newestReleaseDate := time.Time{}
+
+       for _, v := range versions {
+               releaseDate, err := time.Parse(time.RFC3339, v.Time)
+               if err != nil {
+                       continue
+               }
+
+               if newestVersion == "" || releaseDate.After(newestReleaseDate) {
+                       newestVersion = v.Version
+                       newestReleaseDate = releaseDate
+               }
+       }
+
+       return newestVersion
+}
+
+// versionNames returns the version strings of the provided version 
information.
+func versionNames(versions []versionInfo) []string {
+       names := make([]string, 0, len(versions))
+       for _, v := range versions {
+               names = append(names, v.Version)
+       }
+
+       return names
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/go/module/version_test.go 
new/updatecli-0.120.1/pkg/plugins/resources/go/module/version_test.go
--- old/updatecli-0.120.0/pkg/plugins/resources/go/module/version_test.go       
1970-01-01 01:00:00.000000000 +0100
+++ new/updatecli-0.120.1/pkg/plugins/resources/go/module/version_test.go       
2026-08-13 21:59:35.000000000 +0200
@@ -0,0 +1,292 @@
+package gomodule
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "net/http/httptest"
+       "path"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+       "github.com/updatecli/updatecli/pkg/plugins/utils/age"
+       "github.com/updatecli/updatecli/pkg/plugins/utils/version"
+)
+
+const testModule string = "example.com/mymodule"
+
+// daysAgo returns a release date as reported by a Go module proxy.
+func daysAgo(days int) string {
+       return time.Now().Add(-time.Duration(days) * 24 * 
time.Hour).Format(time.RFC3339)
+}
+
+// goProxyStub serves the subset of the goproxy protocol used by that 
resource, as
+// described on https://go.dev/ref/mod#goproxy-protocol
+type goProxyStub struct {
+       // publishedVersions is served by the "@v/list" endpoint.
+       publishedVersions []string
+       // releaseDates maps a version to its release date, served by 
"@v/<version>.info".
+       releaseDates map[string]string
+       // latestVersion is served by the "@latest" endpoint.
+       latestVersion versionInfo
+       // requestedPaths records every requested path, to assert on the 
requests really sent.
+       requestedPaths []string
+}
+
+// start returns a GoModule querying that stub instead of a real Go module 
proxy.
+func (s *goProxyStub) start(t *testing.T, spec Spec) *GoModule {
+       t.Helper()
+
+       server := httptest.NewServer(http.HandlerFunc(func(w 
http.ResponseWriter, r *http.Request) {
+               s.requestedPaths = append(s.requestedPaths, r.URL.Path)
+
+               switch {
+               case strings.HasSuffix(r.URL.Path, "/@v/list"):
+                       fmt.Fprint(w, strings.Join(s.publishedVersions, "\n"))
+
+               case strings.HasSuffix(r.URL.Path, "/@latest"):
+                       if s.latestVersion.Version == "" {
+                               http.NotFound(w, r)
+                               return
+                       }
+                       assert.NoError(t, 
json.NewEncoder(w).Encode(s.latestVersion))
+
+               case strings.HasSuffix(r.URL.Path, ".info"):
+                       requestedVersion := 
strings.TrimSuffix(path.Base(r.URL.Path), ".info")
+                       releaseDate, ok := s.releaseDates[requestedVersion]
+                       if !ok {
+                               http.NotFound(w, r)
+                               return
+                       }
+                       assert.NoError(t, json.NewEncoder(w).Encode(versionInfo{
+                               Version: requestedVersion,
+                               Time:    releaseDate,
+                       }))
+
+               default:
+                       http.NotFound(w, r)
+               }
+       }))
+       t.Cleanup(server.Close)
+
+       spec.Proxy = server.URL
+       spec.Module = testModule
+
+       got, err := New(spec)
+       require.NoError(t, err)
+       got.webClient = http.DefaultClient
+
+       return got
+}
+
+// infoRequests returns the ".info" paths requested to the stub.
+func (s *goProxyStub) infoRequests() []string {
+       requests := []string{}
+       for _, p := range s.requestedPaths {
+               if strings.HasSuffix(p, ".info") {
+                       requests = append(requests, p)
+               }
+       }
+
+       return requests
+}
+
+func TestVersions(t *testing.T) {
+       tests := []struct {
+               name string
+               spec Spec
+               stub goProxyStub
+               // expectedVersion is the version returned by versions()
+               expectedVersion string
+               // expectedHeldBackByAge is true when the age filter is 
expected to discard every version
+               expectedHeldBackByAge bool
+               // expectedInfoRequests is the number of ".info" requests 
expected on the proxy
+               expectedInfoRequests int
+       }{
+               {
+                       name: "module without any published version falls back 
to its latest commit",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.LATESTVERSIONKIND},
+                               Age:           age.Spec{Minimum: "7d"},
+                       },
+                       stub: goProxyStub{
+                               latestVersion: versionInfo{
+                                       Version: 
"v0.0.0-20260101120000-abcdefabcdef",
+                                       Time:    daysAgo(60),
+                               },
+                       },
+                       expectedVersion: "v0.0.0-20260101120000-abcdefabcdef",
+                       // An empty "@v/list" must not be turned into a request 
for "@v/.info"
+                       expectedInfoRequests: 0,
+               },
+               {
+                       name: "cooldown holds back the only commit of a module 
without any published version",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.LATESTVERSIONKIND},
+                               Age:           age.Spec{Minimum: "7d"},
+                       },
+                       stub: goProxyStub{
+                               latestVersion: versionInfo{
+                                       Version: 
"v0.0.0-20260101120000-abcdefabcdef",
+                                       Time:    daysAgo(1),
+                               },
+                       },
+                       expectedHeldBackByAge: true,
+                       expectedInfoRequests:  0,
+               },
+               {
+                       name: "latest endpoint answering a tagged version is 
accepted without any age filter",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.LATESTVERSIONKIND},
+                       },
+                       stub: goProxyStub{
+                               latestVersion: versionInfo{Version: "v1.2.3", 
Time: daysAgo(60)},
+                       },
+                       expectedVersion:      "v1.2.3",
+                       expectedInfoRequests: 0,
+               },
+               {
+                       name: "cooldown holds back every published version 
instead of using the latest commit",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.LATESTVERSIONKIND},
+                               Age:           age.Spec{Minimum: "7d"},
+                       },
+                       stub: goProxyStub{
+                               publishedVersions: []string{"v1.0.0", "v1.1.0"},
+                               releaseDates: map[string]string{
+                                       "v1.0.0": daysAgo(2),
+                                       "v1.1.0": daysAgo(1),
+                               },
+                               // A module publishing versions must never fall 
back to a pseudo version
+                               latestVersion: versionInfo{
+                                       Version: 
"v0.0.0-20260101120000-abcdefabcdef",
+                                       Time:    daysAgo(60),
+                               },
+                       },
+                       expectedHeldBackByAge: true,
+                       expectedInfoRequests:  2,
+               },
+               {
+                       name: "latest filter falls back to the most recently 
published version out of cooldown",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.LATESTVERSIONKIND},
+                               Age:           age.Spec{Minimum: "7d"},
+                       },
+                       stub: goProxyStub{
+                               publishedVersions: []string{"v1.9.0", 
"v1.10.0", "v1.11.0"},
+                               releaseDates: map[string]string{
+                                       "v1.9.0":  daysAgo(50),
+                                       "v1.10.0": daysAgo(10),
+                                       "v1.11.0": daysAgo(1),
+                               },
+                       },
+                       // "v1.9.0" is the lexicographic maximum, "v1.10.0" the 
most recently published
+                       expectedVersion:      "v1.10.0",
+                       expectedInfoRequests: 3,
+               },
+               {
+                       name: "semver filter keeps ordering versions 
semantically",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.SEMVERVERSIONKIND, Pattern: "*"},
+                               Age:           age.Spec{Minimum: "7d"},
+                       },
+                       stub: goProxyStub{
+                               publishedVersions: []string{"v1.9.0", 
"v1.10.0", "v1.11.0"},
+                               releaseDates: map[string]string{
+                                       "v1.9.0":  daysAgo(50),
+                                       "v1.10.0": daysAgo(10),
+                                       "v1.11.0": daysAgo(1),
+                               },
+                       },
+                       expectedVersion:      "v1.10.0",
+                       expectedInfoRequests: 3,
+               },
+               {
+                       name: "no age filter means no release date lookup",
+                       spec: Spec{
+                               VersionFilter: version.Filter{Kind: 
version.SEMVERVERSIONKIND, Pattern: "*"},
+                       },
+                       stub: goProxyStub{
+                               publishedVersions: []string{"v1.9.0", 
"v1.10.0"},
+                       },
+                       expectedVersion:      "v1.10.0",
+                       expectedInfoRequests: 0,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got := tt.stub.start(t, tt.spec)
+
+                       gotVersion, err := got.versions(context.Background())
+
+                       if tt.expectedHeldBackByAge {
+                               require.ErrorIs(t, err, ErrNoVersionMatchingAge)
+                               assert.Empty(t, gotVersion)
+                       } else {
+                               require.NoError(t, err)
+                               assert.Equal(t, tt.expectedVersion, gotVersion)
+                       }
+
+                       assert.Len(t, tt.stub.infoRequests(), 
tt.expectedInfoRequests)
+               })
+       }
+}
+
+func TestNewVersionFilter(t *testing.T) {
+       tests := []struct {
+               name             string
+               versionFilter    version.Filter
+               expectedFilter   version.Filter
+               expectedNewErr   bool
+               expectedIsLatest bool
+       }{
+               {
+                       name:             "no filter falls back to semantic 
versioning",
+                       expectedFilter:   version.Filter{Kind: 
version.SEMVERVERSIONKIND, Pattern: "*"},
+                       expectedIsLatest: true,
+               },
+               {
+                       // A "latest" kind without any pattern used to be 
searched as the literal
+                       // pattern "" and never matched anything.
+                       name:             "latest kind without pattern gets its 
default pattern",
+                       versionFilter:    version.Filter{Kind: 
version.LATESTVERSIONKIND},
+                       expectedFilter:   version.Filter{Kind: 
version.LATESTVERSIONKIND, Pattern: version.LATESTVERSIONKIND},
+                       expectedIsLatest: true,
+               },
+               {
+                       name:             "semver kind without pattern gets its 
default pattern",
+                       versionFilter:    version.Filter{Kind: 
version.SEMVERVERSIONKIND},
+                       expectedFilter:   version.Filter{Kind: 
version.SEMVERVERSIONKIND, Pattern: "*"},
+                       expectedIsLatest: true,
+               },
+               {
+                       name:           "explicit filter is left untouched",
+                       versionFilter:  version.Filter{Kind: 
version.SEMVERVERSIONKIND, Pattern: "1.0.x"},
+                       expectedFilter: version.Filter{Kind: 
version.SEMVERVERSIONKIND, Pattern: "1.0.x"},
+               },
+               {
+                       name:           "unsupported kind is rejected",
+                       versionFilter:  version.Filter{Kind: "notakind"},
+                       expectedNewErr: true,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got, err := New(Spec{Module: testModule, VersionFilter: 
tt.versionFilter})
+                       if tt.expectedNewErr {
+                               require.Error(t, err)
+                               return
+                       }
+
+                       require.NoError(t, err)
+                       assert.Equal(t, tt.expectedFilter, got.versionFilter)
+                       assert.Equal(t, tt.expectedIsLatest, 
isLatestVersionFilter(got.versionFilter))
+               })
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/temurin/main.go 
new/updatecli-0.120.1/pkg/plugins/resources/temurin/main.go
--- old/updatecli-0.120.0/pkg/plugins/resources/temurin/main.go 2026-08-05 
07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/temurin/main.go 2026-08-13 
21:59:35.000000000 +0200
@@ -143,6 +143,7 @@
                ReleaseType:     t.spec.ReleaseType,
                ImageType:       t.spec.ImageType,
                FeatureVersion:  t.spec.FeatureVersion,
+               SpecificVersion: t.spec.SpecificVersion,
                ReleaseLine:     t.spec.ReleaseLine,
                Architecture:    t.spec.Architecture,
                OperatingSystem: t.spec.OperatingSystem,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/temurin/main_test.go 
new/updatecli-0.120.1/pkg/plugins/resources/temurin/main_test.go
--- old/updatecli-0.120.0/pkg/plugins/resources/temurin/main_test.go    
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/temurin/main_test.go    
2026-08-13 21:59:35.000000000 +0200
@@ -7,6 +7,27 @@
        "github.com/stretchr/testify/require"
 )
 
+// The source cache keys entries on ReportConfig, so every spec field that
+// changes what the API returns must be reported (issue #9821).
+func TestReportConfig(t *testing.T) {
+       spec := Spec{
+               ReleaseLine:     "lts",
+               ReleaseType:     "ga",
+               FeatureVersion:  21,
+               SpecificVersion: "21.0.5+11",
+               Result:          "installer_url",
+               Architecture:    "x64",
+               ImageType:       "jdk",
+               OperatingSystem: "windows",
+               Project:         "jdk",
+               Platforms:       []string{"windows/x64"},
+       }
+
+       got := (&Temurin{spec: spec}).ReportConfig()
+
+       assert.Equal(t, spec, got)
+}
+
 func TestNew(t *testing.T) {
        tests := []struct {
                name     string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/yaml/condition.go 
new/updatecli-0.120.1/pkg/plugins/resources/yaml/condition.go
--- old/updatecli-0.120.0/pkg/plugins/resources/yaml/condition.go       
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/yaml/condition.go       
2026-08-13 21:59:35.000000000 +0200
@@ -115,6 +115,13 @@
                                docs = append(docs, &doc)
                        }
 
+                       // Track whether the key was found in at least one 
document of this
+                       // file. The yamlpath engine does not report an error 
when the key is
+                       // missing: it returns an empty result set. For keyonly 
conditions we
+                       // record a miss per file (the same way the go-yaml 
engine does) so the
+                       // handling below can distinguish "at least one file 
contains the key"
+                       // (searchpattern) from "no file contains it".
+                       fileKeyFound := false
                        for index, doc := range docs {
                                if y.spec.DocumentIndex != nil {
                                        if index != *y.spec.DocumentIndex {
@@ -138,6 +145,15 @@
                                for i := range founds {
                                        results = append(results, 
founds[i].Value)
                                }
+
+                               if len(founds) > 0 {
+                                       fileKeyFound = true
+                               }
+                       }
+
+                       if !fileKeyFound && y.spec.KeyOnly {
+                               errorMessages = append(errorMessages,
+                                       fmt.Errorf("%q - %w", originalFilePath, 
ErrKeyNotFound))
                        }
 
                default:
@@ -145,6 +161,11 @@
                }
        }
 
+       originalFilePaths := make([]string, 0, len(y.files))
+       for i := range y.files {
+               originalFilePaths = append(originalFilePaths, 
y.files[i].originalFilePath)
+       }
+
        if len(errorMessages) > 0 {
                if y.spec.KeyOnly {
                        for i := range errorMessages {
@@ -153,17 +174,20 @@
                                        return false, "", 
errorsToError(errorMessages)
                                }
                        }
+
+                       // When a search pattern is used, the condition passes 
as soon as at
+                       // least one file contains the key, aligning with the 
file plugin
+                       // behavior. Otherwise every specified file must 
contain the key.
+                       if y.spec.SearchPattern && len(results) > 0 {
+                               return true, fmt.Sprintf("key %q found in yaml 
file(s) [%q]", y.spec.Key, strings.Join(originalFilePaths, ",")), nil
+                       }
+
                        return false, "key not found in yaml file(s)", nil
                }
 
                return false, "", errorsToError(errorMessages)
        }
 
-       originalFilePaths := make([]string, len(y.files))
-       for i := range y.files {
-               originalFilePaths = append(originalFilePaths, 
y.files[i].originalFilePath)
-       }
-
        // When user want to only check the existence of a YAML key
        if y.spec.KeyOnly {
                if len(results) == len(y.files) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/updatecli-0.120.0/pkg/plugins/resources/yaml/condition_test.go 
new/updatecli-0.120.1/pkg/plugins/resources/yaml/condition_test.go
--- old/updatecli-0.120.0/pkg/plugins/resources/yaml/condition_test.go  
2026-08-05 07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/pkg/plugins/resources/yaml/condition_test.go  
2026-08-13 21:59:35.000000000 +0200
@@ -3,10 +3,13 @@
 import (
        "context"
        "fmt"
+       "os"
+       "path/filepath"
        "testing"
 
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
+       "github.com/updatecli/updatecli/pkg/core/pipeline/scm"
        "github.com/updatecli/updatecli/pkg/core/text"
 )
 
@@ -629,11 +632,161 @@
                        },
                        isResultWanted: true,
                },
+               {
+                       name: "Passing case with keyonly and searchpattern when 
at least one file contains the key (yamlpath)",
+                       spec: Spec{
+                               Files: []string{
+                                       "*.yaml",
+                               },
+                               Key:           "$.github.owner",
+                               KeyOnly:       true,
+                               Engine:        "yamlpath",
+                               SearchPattern: true,
+                       },
+                       files: map[string]file{
+                               "test.yaml": {
+                                       filePath:         "test.yaml",
+                                       originalFilePath: "test.yaml",
+                               },
+                               "too-much.yaml": {
+                                       filePath:         "too-much.yaml",
+                                       originalFilePath: "too-much.yaml",
+                               },
+                       },
+                       mockedContents: map[string]string{
+                               "test.yaml": `---
+github:
+  owner: olblak
+  repository: charts
+`,
+                               "too-much.yaml": `---
+name: github
+`,
+                       },
+                       isResultWanted: true,
+               },
+               {
+                       name: "Passing case with keyonly and searchpattern when 
at least one file contains the key (go-yaml)",
+                       spec: Spec{
+                               Files: []string{
+                                       "*.yaml",
+                               },
+                               Key:           "$.github.owner",
+                               KeyOnly:       true,
+                               SearchPattern: true,
+                       },
+                       files: map[string]file{
+                               "test.yaml": {
+                                       filePath:         "test.yaml",
+                                       originalFilePath: "test.yaml",
+                               },
+                               "too-much.yaml": {
+                                       filePath:         "too-much.yaml",
+                                       originalFilePath: "too-much.yaml",
+                               },
+                       },
+                       mockedContents: map[string]string{
+                               "test.yaml": `---
+github:
+  owner: olblak
+  repository: charts
+`,
+                               "too-much.yaml": `---
+name: github
+`,
+                       },
+                       isResultWanted: true,
+               },
+               {
+                       name: "Failing case with keyonly and searchpattern when 
no file contains the key",
+                       spec: Spec{
+                               Files: []string{
+                                       "*.yaml",
+                               },
+                               Key:           "$.github.owner",
+                               KeyOnly:       true,
+                               Engine:        "yamlpath",
+                               SearchPattern: true,
+                       },
+                       files: map[string]file{
+                               "test.yaml": {
+                                       filePath:         "test.yaml",
+                                       originalFilePath: "test.yaml",
+                               },
+                               "too-much.yaml": {
+                                       filePath:         "too-much.yaml",
+                                       originalFilePath: "too-much.yaml",
+                               },
+                       },
+                       mockedContents: map[string]string{
+                               "test.yaml": `---
+name: github
+`,
+                               "too-much.yaml": `---
+name: updatecli
+`,
+                       },
+                       isResultWanted: false,
+               },
+               {
+                       name: "Failing case with keyonly and multiple files 
when not all files contain the key (no searchpattern)",
+                       spec: Spec{
+                               Files: []string{
+                                       "test.yaml",
+                                       "too-much.yaml",
+                               },
+                               Key:     "$.github.owner",
+                               KeyOnly: true,
+                               Engine:  "yamlpath",
+                       },
+                       files: map[string]file{
+                               "test.yaml": {
+                                       filePath:         "test.yaml",
+                                       originalFilePath: "test.yaml",
+                               },
+                               "too-much.yaml": {
+                                       filePath:         "too-much.yaml",
+                                       originalFilePath: "too-much.yaml",
+                               },
+                       },
+                       mockedContents: map[string]string{
+                               "test.yaml": `---
+github:
+  owner: olblak
+  repository: charts
+`,
+                               "too-much.yaml": `---
+name: github
+`,
+                       },
+                       isResultWanted: false,
+               },
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
+                       mockedContents := tt.mockedContents
+                       var scmHandler scm.ScmHandler
+
+                       // When a search pattern is used, initFiles resolves 
the pattern
+                       // against the working directory, so we need real files 
on disk.
+                       if tt.spec.SearchPattern {
+                               tempDir := t.TempDir()
+                               for fileName := range tt.files {
+                                       if err := 
os.WriteFile(filepath.Join(tempDir, fileName), []byte{}, 0600); err != nil {
+                                               t.Fatalf("failed to create temp 
file: %v", err)
+                                       }
+                               }
+                               scmHandler = &scm.MockScm{WorkingDir: tempDir}
+
+                               updatedContents := make(map[string]string)
+                               for fileName, content := range 
tt.mockedContents {
+                                       updatedContents[filepath.Join(tempDir, 
fileName)] = content
+                               }
+                               mockedContents = updatedContents
+                       }
+
                        mockedText := text.MockTextRetriever{
-                               Contents: tt.mockedContents,
+                               Contents: mockedContents,
                                Err:      tt.mockedError,
                        }
 
@@ -643,7 +796,7 @@
 
                        assert.NoError(t, err)
 
-                       gotResult, _, gotErr := 
y.Condition(context.Background(), tt.inputSourceValue, nil)
+                       gotResult, _, gotErr := 
y.Condition(context.Background(), tt.inputSourceValue, scmHandler)
                        if tt.isErrorWanted {
                                assert.Error(t, gotErr)
                                return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/updatecli-0.120.0/updatecli-compose.yaml 
new/updatecli-0.120.1/updatecli-compose.yaml
--- old/updatecli-0.120.0/updatecli-compose.yaml        2026-08-05 
07:03:11.000000000 +0200
+++ new/updatecli-0.120.1/updatecli-compose.yaml        2026-08-13 
21:59:35.000000000 +0200
@@ -55,7 +55,7 @@
 
   - name: Update Updatecli version in GitHub action
     id: updatecli_github_action
-    policy: 
ghcr.io/updatecli/policies/updatecli/githubaction:0.9.0@sha256:5d21b1af9a9c3c1ed0c05bf0e12b4e3e6d98050d3d6a65bda0f12f7adce94c69
+    policy: 
ghcr.io/updatecli/policies/updatecli/githubaction:0.9.1@sha256:49c07d724474ff7976f7ad5ecb0db9aa46448b456d08f82717913b403a0f02d8
     valuesinline:
       spec:
         rootdir: ".github"

++++++ updatecli.obsinfo ++++++
--- /var/tmp/diff_new_pack.6axwXz/_old  2026-08-28 19:53:46.769392779 +0200
+++ /var/tmp/diff_new_pack.6axwXz/_new  2026-08-28 19:53:46.774392954 +0200
@@ -1,5 +1,5 @@
 name: updatecli
-version: 0.120.0
-mtime: 1785906191
-commit: 6dcc92d2b1d89d81449865291a38cbe858b78905
+version: 0.120.1
+mtime: 1786651175
+commit: 5609a74b86406126f41f74e4d50f1481dbe81fd4
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/updatecli/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.updatecli.new.1265/vendor.tar.gz differ: char 36, 
line 1

Reply via email to