Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package melange for openSUSE:Factory checked 
in at 2026-04-02 17:42:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/melange (Old)
 and      /work/SRC/openSUSE:Factory/.melange.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "melange"

Thu Apr  2 17:42:46 2026 rev:150 rq:1344282 version:0.48.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/melange/melange.changes  2026-03-30 
18:35:50.114436402 +0200
+++ /work/SRC/openSUSE:Factory/.melange.new.21863/melange.changes       
2026-04-02 17:43:55.294185582 +0200
@@ -1,0 +2,16 @@
+Wed Apr 01 04:27:02 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.48.0:
+  * sca: Adjust generate{Perl,Ruby}Deps to account for non-usrmerge
+    (#2454)
+  * build(deps): bump the actions group across 1 directory with 2
+    updates (#2452)
+  * build(deps): bump the gomod group across 1 directory with 4
+    updates (#2451)
+  * sca: Generate Perl dependency (#2453)
+  * feat(qemu): optional build observability event logging (#2394)
+  * build(deps): bump github.com/go-git/go-git/v5 from 5.17.0 to
+    5.17.1 (#2449)
+  * qemu: resolve vmlinuz symlinks and try to fix them (#2445)
+
+-------------------------------------------------------------------

Old:
----
  melange-0.47.0.obscpio

New:
----
  melange-0.48.0.obscpio

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

Other differences:
------------------
++++++ melange.spec ++++++
--- /var/tmp/diff_new_pack.phWb2k/_old  2026-04-02 17:43:56.410231855 +0200
+++ /var/tmp/diff_new_pack.phWb2k/_new  2026-04-02 17:43:56.410231855 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           melange
-Version:        0.47.0
+Version:        0.48.0
 Release:        0
 Summary:        Build APKs from source code
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.phWb2k/_old  2026-04-02 17:43:56.446233347 +0200
+++ /var/tmp/diff_new_pack.phWb2k/_new  2026-04-02 17:43:56.450233513 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/chainguard-dev/melange.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v0.47.0</param>
+    <param name="revision">refs/tags/v0.48.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.phWb2k/_old  2026-04-02 17:43:56.502235670 +0200
+++ /var/tmp/diff_new_pack.phWb2k/_new  2026-04-02 17:43:56.514236167 +0200
@@ -3,6 +3,6 @@
                 <param 
name="url">https://github.com/chainguard-dev/melange</param>
               <param 
name="changesrevision">3f6115b820985d70ca3c93cdf8519c1b3b4cfe81</param></service><service
 name="tar_scm">
                 <param 
name="url">https://github.com/chainguard-dev/melange.git</param>
-              <param 
name="changesrevision">3fa79327ebf36fb4b3700d6a053b95836e45ccd8</param></service></servicedata>
+              <param 
name="changesrevision">54faa684108e9d1e0a716adc3819e105da614bf7</param></service></servicedata>
 (No newline at EOF)
 

++++++ melange-0.47.0.obscpio -> melange-0.48.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/go.mod new/melange-0.48.0/go.mod
--- old/melange-0.47.0/go.mod   2026-03-27 00:06:38.000000000 +0100
+++ new/melange-0.48.0/go.mod   2026-03-31 23:10:54.000000000 +0200
@@ -3,16 +3,16 @@
 go 1.25.7
 
 require (
-       chainguard.dev/apko v1.1.16
+       chainguard.dev/apko v1.2.0
        github.com/chainguard-dev/clog v1.8.0
        github.com/chainguard-dev/go-pkgconfig 
v0.0.0-20240404163941-6351b37b2a10
-       github.com/chainguard-dev/yam v0.2.53
+       github.com/chainguard-dev/yam v0.2.54
        github.com/charmbracelet/log v1.0.0
        github.com/docker/cli v29.3.1+incompatible
        github.com/docker/docker v28.5.2+incompatible
        github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4
        github.com/github/go-spdx/v2 v2.4.0
-       github.com/go-git/go-git/v5 v5.17.0
+       github.com/go-git/go-git/v5 v5.17.2
        github.com/google/go-cmp v0.7.0
        github.com/google/go-containerregistry v0.21.3
        github.com/google/licenseclassifier/v2 v2.0.0
@@ -47,16 +47,14 @@
        gopkg.in/ini.v1 v1.67.1
        gopkg.in/yaml.v3 v3.0.1
        mvdan.cc/sh/v3 v3.13.0
-       sigs.k8s.io/release-utils v0.12.3
+       sigs.k8s.io/release-utils v0.12.4
        sigs.k8s.io/yaml v1.6.0
 )
 
 require (
-       github.com/clipperhouse/stringish v0.1.1 // indirect
-       github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
+       github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
        github.com/containerd/errdefs v1.0.0 // indirect
        github.com/containerd/errdefs/pkg v0.3.0 // indirect
-       github.com/fatih/color v1.18.0 // indirect
        github.com/google/martian/v3 v3.3.3 // indirect
        github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus 
v1.1.0 // indirect
        github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
@@ -78,7 +76,7 @@
 
 require (
        chainguard.dev/go-grpc-kit v0.17.16 // indirect
-       chainguard.dev/sdk v0.1.51 // indirect
+       chainguard.dev/sdk v0.1.52 // indirect
        cloud.google.com/go/auth v0.18.2 // indirect
        cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
        cloud.google.com/go/compute/metadata v0.9.0 // indirect
@@ -123,7 +121,7 @@
        github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
        github.com/google/uuid v1.6.0 // indirect
        github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
-       github.com/googleapis/gax-go/v2 v2.18.0 // indirect
+       github.com/googleapis/gax-go/v2 v2.19.0 // indirect
        github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
        github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
        github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
@@ -133,7 +131,6 @@
        github.com/kevinburke/ssh_config v1.4.0 // indirect
        github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
        github.com/mailru/easyjson v0.9.0 // indirect
-       github.com/mattn/go-colorable v0.1.14 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
        github.com/mattn/go-runewidth v0.0.19 // indirect
        github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -172,9 +169,9 @@
        golang.org/x/mod v0.34.0 // indirect
        golang.org/x/net v0.52.0 // indirect
        golang.org/x/oauth2 v0.36.0 // indirect
-       google.golang.org/api v0.272.0 // indirect
-       google.golang.org/genproto/googleapis/api 
v0.0.0-20260311181403-84a4fc48630c // indirect
-       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260311181403-84a4fc48630c // indirect
+       google.golang.org/api v0.273.0 // indirect
+       google.golang.org/genproto/googleapis/api 
v0.0.0-20260316180232-0b37fe3546d5 // indirect
+       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260319201613-d00831a3d3e7 // indirect
        google.golang.org/grpc v1.79.3 // indirect
        google.golang.org/protobuf v1.36.11
        gopkg.in/warnings.v0 v0.1.2 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/go.sum new/melange-0.48.0/go.sum
--- old/melange-0.47.0/go.sum   2026-03-27 00:06:38.000000000 +0100
+++ new/melange-0.48.0/go.sum   2026-03-31 23:10:54.000000000 +0200
@@ -1,9 +1,9 @@
-chainguard.dev/apko v1.1.16 h1:edh6bjoFW2Ysyj15NhTG1LWWtpByUgUmIjdmLmeTIj8=
-chainguard.dev/apko v1.1.16/go.mod 
h1:Ls6WmnS+JeXUMM/Zo4LV/qu3cxZA25CjYf88llinJl8=
+chainguard.dev/apko v1.2.0 h1:44uI/EBIjMuOwdwxDCbIpY1bJrSOLEVtRqM9SUcz69E=
+chainguard.dev/apko v1.2.0/go.mod 
h1:mLYcD45WyC55MGyjeJSju1+FmxzZFB9LCAnZzbRp8WU=
 chainguard.dev/go-grpc-kit v0.17.16 
h1:Y9RKwZCnrYR3S0K8BiazyOoBrZF+Q7bJWDacfKXz2zg=
 chainguard.dev/go-grpc-kit v0.17.16/go.mod 
h1:0vrfIBJguXNa+EKOUEhx1Fj2aBp8o6A8gAHoidiF8ps=
-chainguard.dev/sdk v0.1.51 h1:rB2rBqExsz6ydbEbMlK/Lm90PSik57lQ40sQlmu1yAs=
-chainguard.dev/sdk v0.1.51/go.mod 
h1:EFyAQrI1kR73wQVmOFzBr473Ka3gXkzzXuvjJaHuNcM=
+chainguard.dev/sdk v0.1.52 h1:G1wmZHU8v5E78YlCHuwQH0Hwt4NBBCvCNAFad5FUanQ=
+chainguard.dev/sdk v0.1.52/go.mod 
h1:IZIiUyuNaxTao6mpC/BROsw/dwjl/DmCR/raIT7eK4c=
 cloud.google.com/go v0.26.0/go.mod 
h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go/auth v0.18.2 
h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
 cloud.google.com/go/auth v0.18.2/go.mod 
h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
@@ -44,8 +44,8 @@
 github.com/chainguard-dev/clog v1.8.0/go.mod 
h1:5MQOZi+Iu7fV7GcJG8ag8rCB5elEOpqRMKEASgnGVdo=
 github.com/chainguard-dev/go-pkgconfig v0.0.0-20240404163941-6351b37b2a10 
h1:XR2vgQC024I9/boh9r1ihVv8Z14+pbvWqXeYMCnZJpc=
 github.com/chainguard-dev/go-pkgconfig 
v0.0.0-20240404163941-6351b37b2a10/go.mod 
h1:1p6+MesLcjKeON5BRWa7I87mvAY0QmKjgginIM3w6BI=
-github.com/chainguard-dev/yam v0.2.53 
h1:98wzR0hTZmOu8yl77/D+U9vO4ZB2TbTeq0deD3Zqy8s=
-github.com/chainguard-dev/yam v0.2.53/go.mod 
h1:Sbt8pVO8DbHoVly44oF5gg03NRxl9AhEImOkqGyoQCs=
+github.com/chainguard-dev/yam v0.2.54 
h1:QIzSAllLHUCx0s3Ot1PpdcY7c6jhiWAEP6SAqOIN6xs=
+github.com/chainguard-dev/yam v0.2.54/go.mod 
h1:Sbt8pVO8DbHoVly44oF5gg03NRxl9AhEImOkqGyoQCs=
 github.com/charmbracelet/colorprofile v0.3.2 
h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
 github.com/charmbracelet/colorprofile v0.3.2/go.mod 
h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
 github.com/charmbracelet/lipgloss v1.1.0 
h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
@@ -59,10 +59,8 @@
 github.com/charmbracelet/x/term v0.2.1 
h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
 github.com/charmbracelet/x/term v0.2.1/go.mod 
h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
 github.com/client9/misspell v0.3.4/go.mod 
h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/clipperhouse/stringish v0.1.1 
h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
-github.com/clipperhouse/stringish v0.1.1/go.mod 
h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
-github.com/clipperhouse/uax29/v2 v2.3.0 
h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
-github.com/clipperhouse/uax29/v2 v2.3.0/go.mod 
h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/clipperhouse/uax29/v2 v2.6.0 
h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
+github.com/clipperhouse/uax29/v2 v2.6.0/go.mod 
h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
 github.com/cloudflare/circl v1.6.3 
h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
 github.com/cloudflare/circl v1.6.3/go.mod 
h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod 
h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -131,8 +129,8 @@
 github.com/go-git/go-billy/v5 v5.8.0/go.mod 
h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 
h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
 github.com/go-git/go-git-fixtures/v4 
v4.3.2-0.20231010084843-55a94097c399/go.mod 
h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
-github.com/go-git/go-git/v5 v5.17.0 
h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
-github.com/go-git/go-git/v5 v5.17.0/go.mod 
h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
+github.com/go-git/go-git/v5 v5.17.2 
h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104=
+github.com/go-git/go-git/v5 v5.17.2/go.mod 
h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
 github.com/go-jose/go-jose/v3 v3.0.4 
h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
 github.com/go-jose/go-jose/v3 v3.0.4/go.mod 
h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-logfmt/logfmt v0.6.1 
h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
@@ -190,8 +188,8 @@
 github.com/google/uuid v1.6.0/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/enterprise-certificate-proxy v0.3.14 
h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
 github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod 
h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
-github.com/googleapis/gax-go/v2 v2.18.0 
h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI=
-github.com/googleapis/gax-go/v2 v2.18.0/go.mod 
h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE=
+github.com/googleapis/gax-go/v2 v2.19.0 
h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
+github.com/googleapis/gax-go/v2 v2.19.0/go.mod 
h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 
h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus 
v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 
h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
@@ -488,17 +486,17 @@
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
 gonum.org/v1/gonum v0.16.0/go.mod 
h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
-google.golang.org/api v0.272.0/go.mod 
h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
+google.golang.org/api v0.273.0 h1:r/Bcv36Xa/te1ugaN1kdJ5LoA5Wj/cL+a4gj6FiPBjQ=
+google.golang.org/api v0.273.0/go.mod 
h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
 google.golang.org/appengine v1.1.0/go.mod 
h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod 
h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod 
h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod 
h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod 
h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c 
h1:OyQPd6I3pN/9gDxz6L13kYGJgqkpdrAohJRBeXyxlgI=
-google.golang.org/genproto/googleapis/api 
v0.0.0-20260311181403-84a4fc48630c/go.mod 
h1:X2gu9Qwng7Nn009s/r3RUxqkzQNqOrAy79bluY7ojIg=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c 
h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk=
-google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260311181403-84a4fc48630c/go.mod 
h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 
h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
+google.golang.org/genproto/googleapis/api 
v0.0.0-20260316180232-0b37fe3546d5/go.mod 
h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 
h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
+google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260319201613-d00831a3d3e7/go.mod 
h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
 google.golang.org/grpc v1.19.0/go.mod 
h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod 
h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod 
h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -545,7 +543,7 @@
 mvdan.cc/sh/v3 v3.13.0/go.mod h1:KV1GByGPc/Ho0X1E6Uz9euhsIQEj4hwyKnodLlFLoDM=
 pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
 pgregory.net/rapid v1.2.0/go.mod 
h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
-sigs.k8s.io/release-utils v0.12.3 
h1:iNVJY81QfmMCmXxMg8IvvkkeQNk6ZWlLj+iPKSlKyVQ=
-sigs.k8s.io/release-utils v0.12.3/go.mod 
h1:BvbNmm1BmM3cnEpBmNHWL3wOSziOdGlsYR8vCFq/Q0o=
+sigs.k8s.io/release-utils v0.12.4 
h1:kuG6WTWGCKx5uUrJwl2uFErOKOw+4Ba8WrPmOQh5J3g=
+sigs.k8s.io/release-utils v0.12.4/go.mod 
h1:Tc3iM9DVM3W9oJu/6rEI+LnREuhy8lZ7wInQhRBtUoo=
 sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
 sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/build/build.go 
new/melange-0.48.0/pkg/build/build.go
--- old/melange-0.47.0/pkg/build/build.go       2026-03-27 00:06:38.000000000 
+0100
+++ new/melange-0.48.0/pkg/build/build.go       2026-03-31 23:10:54.000000000 
+0200
@@ -770,6 +770,16 @@
        }
        log.Infof("retrieved and wrote post-build workspace to: %s", 
b.WorkspaceDir)
 
+       // Retrieve and log build observability events if the observability hook
+       // is installed. Only applicable to QEMU builds which run in a full VM.
+       if b.Runner.Name() == container.QemuName {
+               if obsEvents, err := container.RetrieveObservabilityEvents(ctx, 
cfg); err != nil {
+                       log.Warnf("failed to retrieve observability events: 
%v", err)
+               } else if obsEvents != nil {
+                       container.LogObservabilityEvents(ctx, obsEvents)
+               }
+       }
+
        // perform package linting
        for _, lt := range linterQueue {
                log.Infof("running package linters for %s", lt.pkgName)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/container/observability.go 
new/melange-0.48.0/pkg/container/observability.go
--- old/melange-0.47.0/pkg/container/observability.go   1970-01-01 
01:00:00.000000000 +0100
+++ new/melange-0.48.0/pkg/container/observability.go   2026-03-31 
23:10:54.000000000 +0200
@@ -0,0 +1,232 @@
+// Copyright 2025 Chainguard, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package container
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+       "fmt"
+
+       "github.com/chainguard-dev/clog"
+)
+
+// Well-known paths where the observability framework may write events.
+// The hook package determines which path is used; we probe all of them.
+var observabilityEventPaths = []string{
+       "/var/log/observability/events.json",
+       "/opt/observability-logs/events.log",
+       "/tmp/observability/events.log",
+}
+
+// ObservabilityEvents holds parsed event data retrieved from the build VM.
+type ObservabilityEvents struct {
+       // RawData is the raw NDJSON event data.
+       RawData []byte
+
+       // EventCount is the total number of events.
+       EventCount int
+
+       // NetworkConnections is the list of network connections observed.
+       NetworkConnections []NetworkConnection
+}
+
+// NetworkConnection represents a single observed network connection.
+type NetworkConnection struct {
+       Process   string `json:"process"`
+       Protocol  string `json:"protocol"`
+       SrcAddr   string `json:"src_addr"`
+       SrcPort   uint32 `json:"src_port"`
+       DstAddr   string `json:"dst_addr"`
+       DstPort   uint32 `json:"dst_port"`
+       Family    string `json:"family"`
+       Function  string `json:"function"` // tcp_connect, tcp_close, etc.
+       Timestamp string `json:"timestamp"`
+}
+
+// RetrieveObservabilityEvents fetches observability events from the build VM
+// via the SSHControlClient (port 2223, unchrooted root access). This should
+// be called after the build completes but before TerminatePod.
+//
+// Returns nil with no error if the observability hook is not installed or no
+// events were generated. This makes the feature fully optional — default
+// builds without the hook are completely unaffected.
+func RetrieveObservabilityEvents(ctx context.Context, cfg *Config) 
(*ObservabilityEvents, error) {
+       if cfg.SSHControlClient == nil {
+               return nil, nil
+       }
+
+       log := clog.FromContext(ctx)
+
+       // Probe known event file locations. If none exist, the observability
+       // hook is not installed and we silently return nil.
+       eventsPath := ""
+       for _, path := range observabilityEventPaths {
+               err := sendSSHCommand(ctx, cfg.SSHControlClient, cfg, nil, nil, 
nil, false,
+                       []string{"test", "-f", path})
+               if err == nil {
+                       eventsPath = path
+                       break
+               }
+       }
+
+       if eventsPath == "" {
+               // No events file found — observability hook is not installed.
+               // This is the normal case for default builds; return silently.
+               return nil, nil
+       }
+
+       log.Infof("qemu: found observability events at %s", eventsPath)
+
+       // Cap retrieval to prevent a pathological VM from exhausting host 
memory.
+       // 50 MiB is well above normal builds (9-17k events ≈ a few MiB).
+       const maxEventsBytes = 50 << 20 // 50 MiB
+       var eventsBuf bytes.Buffer
+       err := sendSSHCommand(ctx, cfg.SSHControlClient, cfg, nil, nil, 
&eventsBuf, false,
+               []string{"head", "-c", fmt.Sprintf("%d", maxEventsBytes), 
eventsPath})
+       if err != nil {
+               return nil, fmt.Errorf("failed to retrieve observability 
events: %w", err)
+       }
+
+       rawData := eventsBuf.Bytes()
+       if len(rawData) == 0 {
+               log.Warn("qemu: observability events file exists but is empty")
+               return &ObservabilityEvents{RawData: rawData}, nil
+       }
+
+       // Parse events to extract network connections
+       connections, eventCount := extractNetworkConnections(rawData)
+
+       log.Infof("qemu: retrieved %d observability events (%d network 
connections)", eventCount, len(connections))
+
+       return &ObservabilityEvents{
+               RawData:            rawData,
+               EventCount:         eventCount,
+               NetworkConnections: connections,
+       }, nil
+}
+
+// LogObservabilityEvents writes all observability events to melange's stdout
+// via the structured logger. Each raw event is logged as a separate line with
+// an [OBSERVABILITY] prefix for filtering. Network connections get a dedicated
+// summary section. In the elastic build environment, these log lines flow to
+// Cloud Logging via GKE pod stdout and are individually searchable.
+func LogObservabilityEvents(ctx context.Context, events *ObservabilityEvents) {
+       if events == nil || len(events.RawData) == 0 {
+               return
+       }
+
+       log := clog.FromContext(ctx)
+
+       // Summary header
+       log.Infof("[OBSERVABILITY] === Build Observability Report: %d events, 
%d network connections ===",
+               events.EventCount, len(events.NetworkConnections))
+
+       // Log each network connection (high-value, low-volume)
+       for _, conn := range events.NetworkConnections {
+               log.Infof("[OBSERVABILITY] network: %s %s %s:%d -> %s:%d (%s)",
+                       conn.Function, conn.Process,
+                       conn.SrcAddr, conn.SrcPort,
+                       conn.DstAddr, conn.DstPort,
+                       conn.Protocol)
+       }
+
+       // Log every raw event line (for Cloud Logging ingestion)
+       for line := range bytes.SplitSeq(events.RawData, []byte("\n")) {
+               line = bytes.TrimSpace(line)
+               if len(line) == 0 {
+                       continue
+               }
+               log.Infof("[OBSERVABILITY] %s", string(line))
+       }
+
+       log.Infof("[OBSERVABILITY] === End of observability report ===")
+}
+
+// extractNetworkConnections parses NDJSON observability events and extracts
+// network connection information from kprobe events.
+func extractNetworkConnections(data []byte) ([]NetworkConnection, int) {
+       connections := make([]NetworkConnection, 0)
+       eventCount := 0
+
+       for line := range bytes.SplitSeq(data, []byte("\n")) {
+               line = bytes.TrimSpace(line)
+               if len(line) == 0 {
+                       continue
+               }
+
+               var raw map[string]json.RawMessage
+               if err := json.Unmarshal(line, &raw); err != nil {
+                       continue // Skip malformed lines
+               }
+               eventCount++
+
+               kprobeData, ok := raw["process_kprobe"]
+               if !ok {
+                       continue
+               }
+
+               var kprobe struct {
+                       Process struct {
+                               Binary string `json:"binary"`
+                       } `json:"process"`
+                       FunctionName string `json:"function_name"`
+                       Args         []struct {
+                               SockArg *struct {
+                                       Family   string `json:"family"`
+                                       Protocol string `json:"protocol"`
+                                       SAddr    string `json:"saddr"`
+                                       DAddr    string `json:"daddr"`
+                                       SPort    uint32 `json:"sport"`
+                                       DPort    uint32 `json:"dport"`
+                               } `json:"sock_arg,omitempty"`
+                       } `json:"args"`
+               }
+
+               if err := json.Unmarshal(kprobeData, &kprobe); err != nil {
+                       continue
+               }
+
+               switch kprobe.FunctionName {
+               case "tcp_connect", "tcp_close", "tcp_sendmsg":
+               default:
+                       continue
+               }
+
+               if len(kprobe.Args) == 0 || kprobe.Args[0].SockArg == nil {
+                       continue
+               }
+
+               sock := kprobe.Args[0].SockArg
+               timestamp := ""
+               if t, ok := raw["time"]; ok {
+                       _ = json.Unmarshal(t, &timestamp)
+               }
+
+               connections = append(connections, NetworkConnection{
+                       Process:   kprobe.Process.Binary,
+                       Protocol:  sock.Protocol,
+                       SrcAddr:   sock.SAddr,
+                       SrcPort:   sock.SPort,
+                       DstAddr:   sock.DAddr,
+                       DstPort:   sock.DPort,
+                       Family:    sock.Family,
+                       Function:  kprobe.FunctionName,
+                       Timestamp: timestamp,
+               })
+       }
+
+       return connections, eventCount
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/container/observability_test.go 
new/melange-0.48.0/pkg/container/observability_test.go
--- old/melange-0.47.0/pkg/container/observability_test.go      1970-01-01 
01:00:00.000000000 +0100
+++ new/melange-0.48.0/pkg/container/observability_test.go      2026-03-31 
23:10:54.000000000 +0200
@@ -0,0 +1,424 @@
+// Copyright 2025 Chainguard, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package container
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+       "strings"
+       "testing"
+
+       "github.com/chainguard-dev/clog"
+       "github.com/chainguard-dev/clog/slogtest"
+)
+
+// ObservabilityEvent represents the JSON event structure emitted by the
+// observability framework. Used for event schema validation and test parsing.
+type ObservabilityEvent struct {
+       ProcessExec   *ProcessExec   `json:"process_exec,omitempty"`
+       ProcessKprobe *ProcessKprobe `json:"process_kprobe,omitempty"`
+       ProcessExit   *ProcessExit   `json:"process_exit,omitempty"`
+       NodeName      string         `json:"node_name,omitempty"`
+       Time          string         `json:"time,omitempty"`
+}
+
+type ProcessExec struct {
+       Process *Process `json:"process,omitempty"`
+       Parent  *Process `json:"parent,omitempty"`
+}
+
+type ProcessKprobe struct {
+       Process      *Process    `json:"process,omitempty"`
+       Parent       *Process    `json:"parent,omitempty"`
+       FunctionName string      `json:"function_name,omitempty"`
+       Args         []KprobeArg `json:"args,omitempty"`
+       Action       string      `json:"action,omitempty"`
+       PolicyName   string      `json:"policy_name,omitempty"`
+}
+
+type ProcessExit struct {
+       Process *Process `json:"process,omitempty"`
+       Parent  *Process `json:"parent,omitempty"`
+}
+
+type Process struct {
+       ExecID       string `json:"exec_id,omitempty"`
+       PID          uint32 `json:"pid,omitempty"`
+       UID          uint32 `json:"uid,omitempty"`
+       CWD          string `json:"cwd,omitempty"`
+       Binary       string `json:"binary,omitempty"`
+       Arguments    string `json:"arguments,omitempty"`
+       Flags        string `json:"flags,omitempty"`
+       StartTime    string `json:"start_time,omitempty"`
+       ParentExecID string `json:"parent_exec_id,omitempty"`
+}
+
+type KprobeArg struct {
+       SockArg *KprobeSock `json:"sock_arg,omitempty"`
+       SkbArg  *KprobeSkb  `json:"skb_arg,omitempty"`
+       IntArg  *int64      `json:"int_arg,omitempty"`
+}
+
+type KprobeSock struct {
+       Family   string `json:"family,omitempty"`
+       Type     string `json:"type,omitempty"`
+       Protocol string `json:"protocol,omitempty"`
+       SAddr    string `json:"saddr,omitempty"`
+       DAddr    string `json:"daddr,omitempty"`
+       SPort    uint32 `json:"sport,omitempty"`
+       DPort    uint32 `json:"dport,omitempty"`
+       State    string `json:"state,omitempty"`
+}
+
+type KprobeSkb struct {
+       SAddr    string `json:"saddr,omitempty"`
+       DAddr    string `json:"daddr,omitempty"`
+       SPort    uint32 `json:"sport,omitempty"`
+       DPort    uint32 `json:"dport,omitempty"`
+       Protocol string `json:"protocol,omitempty"`
+       Family   string `json:"family,omitempty"`
+       Len      uint32 `json:"len,omitempty"`
+}
+
+// --- Unit Tests ---
+
+func TestObservabilityEventParsing(t *testing.T) {
+       tests := []struct {
+               name      string
+               input     string
+               wantType  string // "exec", "kprobe", "exit"
+               wantValid bool
+       }{
+               {
+                       name: "process_exec event",
+                       input: `{
+                               "process_exec": {
+                                       "process": {
+                                               "binary": "/usr/bin/gcc",
+                                               "arguments": "-o hello hello.c",
+                                               "pid": 1234
+                                       }
+                               },
+                               "time": "2025-01-01T00:00:00Z"
+                       }`,
+                       wantType:  "exec",
+                       wantValid: true,
+               },
+               {
+                       name: "tcp_connect kprobe event",
+                       input: `{
+                               "process_kprobe": {
+                                       "process": {
+                                               "binary": "/usr/bin/curl",
+                                               "pid": 5678
+                                       },
+                                       "function_name": "tcp_connect",
+                                       "args": [{
+                                               "sock_arg": {
+                                                       "family": "AF_INET",
+                                                       "type": "SOCK_STREAM",
+                                                       "protocol": 
"IPPROTO_TCP",
+                                                       "saddr": "10.0.2.15",
+                                                       "daddr": 
"104.198.14.52",
+                                                       "sport": 48272,
+                                                       "dport": 443
+                                               }
+                                       }],
+                                       "policy_name": "network-monitor"
+                               },
+                               "time": "2025-01-01T00:00:01Z"
+                       }`,
+                       wantType:  "kprobe",
+                       wantValid: true,
+               },
+               {
+                       name: "tcp_close kprobe event",
+                       input: `{
+                               "process_kprobe": {
+                                       "process": {
+                                               "binary": "/usr/bin/wget",
+                                               "pid": 9012
+                                       },
+                                       "function_name": "tcp_close",
+                                       "args": [{
+                                               "sock_arg": {
+                                                       "family": "AF_INET",
+                                                       "type": "SOCK_STREAM",
+                                                       "protocol": 
"IPPROTO_TCP",
+                                                       "daddr": "8.8.8.8",
+                                                       "dport": 53
+                                               }
+                                       }],
+                                       "policy_name": "network-monitor"
+                               },
+                               "time": "2025-01-01T00:00:02Z"
+                       }`,
+                       wantType:  "kprobe",
+                       wantValid: true,
+               },
+               {
+                       name:      "empty JSON",
+                       input:     `{}`,
+                       wantType:  "",
+                       wantValid: true,
+               },
+               {
+                       name:      "invalid JSON",
+                       input:     `{not valid json}`,
+                       wantType:  "",
+                       wantValid: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       var event ObservabilityEvent
+                       err := json.Unmarshal([]byte(tt.input), &event)
+
+                       if tt.wantValid && err != nil {
+                               t.Fatalf("expected valid JSON, got error: %v", 
err)
+                       }
+                       if !tt.wantValid && err == nil {
+                               t.Fatalf("expected invalid JSON, got nil error")
+                       }
+                       if !tt.wantValid {
+                               return
+                       }
+
+                       switch tt.wantType {
+                       case "exec":
+                               if event.ProcessExec == nil {
+                                       t.Error("expected process_exec event, 
got nil")
+                               }
+                               if event.ProcessExec != nil && 
event.ProcessExec.Process == nil {
+                                       t.Error("expected process_exec.process, 
got nil")
+                               }
+                       case "kprobe":
+                               if event.ProcessKprobe == nil {
+                                       t.Error("expected process_kprobe event, 
got nil")
+                               }
+                               if event.ProcessKprobe != nil {
+                                       if event.ProcessKprobe.FunctionName == 
"" {
+                                               t.Error("expected function_name 
in kprobe event")
+                                       }
+                                       if len(event.ProcessKprobe.Args) == 0 {
+                                               t.Error("expected args in 
kprobe event")
+                                       }
+                               }
+                       case "":
+                               // Empty event
+                       default:
+                               t.Fatalf("unknown event type: %s", tt.wantType)
+                       }
+               })
+       }
+}
+
+func TestObservabilityNetworkEventExtraction(t *testing.T) {
+       input := `{
+               "process_kprobe": {
+                       "process": {
+                               "binary": "/usr/bin/apk",
+                               "arguments": "add --no-cache gcc",
+                               "pid": 100
+                       },
+                       "function_name": "tcp_connect",
+                       "args": [{
+                               "sock_arg": {
+                                       "family": "AF_INET",
+                                       "type": "SOCK_STREAM",
+                                       "protocol": "IPPROTO_TCP",
+                                       "saddr": "10.0.2.15",
+                                       "daddr": "packages.wolfi.dev",
+                                       "sport": 54321,
+                                       "dport": 443
+                               }
+                       }],
+                       "policy_name": "network-monitor"
+               },
+               "time": "2025-01-01T00:00:00Z"
+       }`
+
+       var event ObservabilityEvent
+       if err := json.Unmarshal([]byte(input), &event); err != nil {
+               t.Fatalf("failed to parse event: %v", err)
+       }
+
+       if event.ProcessKprobe == nil {
+               t.Fatal("expected kprobe event")
+       }
+
+       kp := event.ProcessKprobe
+       if kp.FunctionName != "tcp_connect" {
+               t.Errorf("function_name = %q, want %q", kp.FunctionName, 
"tcp_connect")
+       }
+       if len(kp.Args) == 0 || kp.Args[0].SockArg == nil {
+               t.Fatal("expected sock_arg in args[0]")
+       }
+
+       sock := kp.Args[0].SockArg
+       if sock.DPort != 443 {
+               t.Errorf("dport = %d, want 443", sock.DPort)
+       }
+       if sock.Protocol != "IPPROTO_TCP" {
+               t.Errorf("protocol = %q, want IPPROTO_TCP", sock.Protocol)
+       }
+}
+
+func TestParseObservabilityEventsFile(t *testing.T) {
+       eventsData := strings.Join([]string{
+               
`{"process_exec":{"process":{"binary":"/bin/sh","pid":1}},"time":"2025-01-01T00:00:00Z"}`,
+               
`{"process_kprobe":{"process":{"binary":"/usr/bin/wget","pid":2},"function_name":"tcp_connect","args":[{"sock_arg":{"daddr":"1.2.3.4","dport":80,"family":"AF_INET","protocol":"IPPROTO_TCP"}}],"policy_name":"network-monitor"},"time":"2025-01-01T00:00:01Z"}`,
+               
`{"process_kprobe":{"process":{"binary":"/usr/bin/apk","pid":3},"function_name":"tcp_connect","args":[{"sock_arg":{"daddr":"5.6.7.8","dport":443,"family":"AF_INET","protocol":"IPPROTO_TCP"}}],"policy_name":"network-monitor"},"time":"2025-01-01T00:00:02Z"}`,
+               
`{"process_exit":{"process":{"binary":"/bin/sh","pid":1}},"time":"2025-01-01T00:00:03Z"}`,
+       }, "\n")
+
+       events, networkEvents, err := 
parseObservabilityEvents([]byte(eventsData))
+       if err != nil {
+               t.Fatalf("parseObservabilityEvents() error: %v", err)
+       }
+
+       if len(events) != 4 {
+               t.Errorf("got %d events, want 4", len(events))
+       }
+       if len(networkEvents) != 2 {
+               t.Errorf("got %d network events, want 2", len(networkEvents))
+       }
+
+       expectedIPs := map[string]uint32{"1.2.3.4": 80, "5.6.7.8": 443}
+       for _, ne := range networkEvents {
+               if ne.ProcessKprobe == nil || len(ne.ProcessKprobe.Args) == 0 
|| ne.ProcessKprobe.Args[0].SockArg == nil {
+                       t.Error("network event missing sock_arg")
+                       continue
+               }
+               sock := ne.ProcessKprobe.Args[0].SockArg
+               wantPort, ok := expectedIPs[sock.DAddr]
+               if !ok {
+                       t.Errorf("unexpected destination IP: %s", sock.DAddr)
+               }
+               if sock.DPort != wantPort {
+                       t.Errorf("for IP %s: dport = %d, want %d", sock.DAddr, 
sock.DPort, wantPort)
+               }
+       }
+}
+
+// --- Tests for observability.go functions ---
+
+func TestExtractNetworkConnections(t *testing.T) {
+       eventsData := strings.Join([]string{
+               
`{"process_exec":{"process":{"binary":"/bin/sh","pid":1}},"time":"2025-01-01T00:00:00Z"}`,
+               
`{"process_kprobe":{"process":{"binary":"/usr/bin/wget"},"function_name":"tcp_connect","args":[{"sock_arg":{"family":"AF_INET","protocol":"IPPROTO_TCP","saddr":"10.0.2.15","daddr":"1.2.3.4","sport":54321,"dport":80}}]},"time":"2025-01-01T00:00:01Z"}`,
+               
`{"process_kprobe":{"process":{"binary":"/usr/bin/apk"},"function_name":"tcp_connect","args":[{"sock_arg":{"family":"AF_INET","protocol":"IPPROTO_TCP","saddr":"10.0.2.15","daddr":"5.6.7.8","sport":54322,"dport":443}}]},"time":"2025-01-01T00:00:02Z"}`,
+               
`{"process_kprobe":{"process":{"binary":"/usr/bin/wget"},"function_name":"tcp_close","args":[{"sock_arg":{"family":"AF_INET","protocol":"IPPROTO_TCP","saddr":"10.0.2.15","daddr":"1.2.3.4","sport":54321,"dport":80}}]},"time":"2025-01-01T00:00:03Z"}`,
+               
`{"process_exit":{"process":{"binary":"/bin/sh","pid":1}},"time":"2025-01-01T00:00:04Z"}`,
+       }, "\n")
+
+       connections, eventCount := extractNetworkConnections([]byte(eventsData))
+       if eventCount != 5 {
+               t.Errorf("eventCount = %d, want 5", eventCount)
+       }
+       if len(connections) != 3 {
+               t.Errorf("len(connections) = %d, want 3", len(connections))
+       }
+       if len(connections) > 0 {
+               c := connections[0]
+               if c.DstAddr != "1.2.3.4" || c.DstPort != 80 {
+                       t.Errorf("connection[0] = %s:%d, want 1.2.3.4:80", 
c.DstAddr, c.DstPort)
+               }
+               if c.Process != "/usr/bin/wget" {
+                       t.Errorf("connection[0].Process = %q, want 
/usr/bin/wget", c.Process)
+               }
+       }
+}
+
+func TestExtractNetworkConnections_EmptyData(t *testing.T) {
+       connections, eventCount := extractNetworkConnections([]byte(""))
+       if eventCount != 0 {
+               t.Errorf("eventCount = %d, want 0", eventCount)
+       }
+       if len(connections) != 0 {
+               t.Errorf("len(connections) = %d, want 0", len(connections))
+       }
+}
+
+func TestExtractNetworkConnections_MalformedLines(t *testing.T) {
+       eventsData := "not 
json\n{\"process_exec\":{\"process\":{\"binary\":\"/bin/sh\"}}}\n{broken\n"
+       connections, eventCount := extractNetworkConnections([]byte(eventsData))
+       if eventCount != 1 {
+               t.Errorf("eventCount = %d, want 1 (only the valid line)", 
eventCount)
+       }
+       if len(connections) != 0 {
+               t.Errorf("len(connections) = %d, want 0 (no network events)", 
len(connections))
+       }
+}
+
+func TestLogObservabilityEvents(t *testing.T) {
+       ctx := clog.WithLogger(context.Background(), slogtest.TestLogger(t))
+
+       events := &ObservabilityEvents{
+               RawData: []byte(strings.Join([]string{
+                       
`{"process_exec":{"process":{"binary":"/bin/sh","pid":1}},"time":"2025-01-01T00:00:00Z"}`,
+                       
`{"process_kprobe":{"process":{"binary":"/usr/bin/wget"},"function_name":"tcp_connect","args":[{"sock_arg":{"daddr":"1.2.3.4","dport":80,"family":"AF_INET","protocol":"IPPROTO_TCP","saddr":"10.0.2.15","sport":54321}}]},"time":"2025-01-01T00:00:01Z"}`,
+               }, "\n")),
+               EventCount: 2,
+               NetworkConnections: []NetworkConnection{
+                       {Process: "/usr/bin/wget", Protocol: "IPPROTO_TCP", 
SrcAddr: "10.0.2.15", SrcPort: 54321, DstAddr: "1.2.3.4", DstPort: 80, Family: 
"AF_INET", Function: "tcp_connect"},
+               },
+       }
+
+       // Should not panic or error — just logs to the test logger
+       LogObservabilityEvents(ctx, events)
+}
+
+func TestLogObservabilityEvents_NilEvents(t *testing.T) {
+       ctx := clog.WithLogger(context.Background(), slogtest.TestLogger(t))
+       // Should be a no-op
+       LogObservabilityEvents(ctx, nil)
+}
+
+func TestLogObservabilityEvents_EmptyRawData(t *testing.T) {
+       ctx := clog.WithLogger(context.Background(), slogtest.TestLogger(t))
+       // Should be a no-op
+       LogObservabilityEvents(ctx, &ObservabilityEvents{})
+}
+
+// --- Helper Functions ---
+
+// parseObservabilityEvents parses NDJSON event data.
+// Returns all events and a filtered list of network-related kprobe events.
+func parseObservabilityEvents(data []byte) ([]ObservabilityEvent, 
[]ObservabilityEvent, error) {
+       allEvents := make([]ObservabilityEvent, 0)
+       var networkEvents []ObservabilityEvent
+
+       for line := range bytes.SplitSeq(data, []byte("\n")) {
+               line = bytes.TrimSpace(line)
+               if len(line) == 0 {
+                       continue
+               }
+               var event ObservabilityEvent
+               if err := json.Unmarshal(line, &event); err != nil {
+                       return nil, nil, err
+               }
+               allEvents = append(allEvents, event)
+               if event.ProcessKprobe != nil {
+                       fn := event.ProcessKprobe.FunctionName
+                       if fn == "tcp_connect" || fn == "tcp_close" || fn == 
"tcp_sendmsg" {
+                               networkEvents = append(networkEvents, event)
+                       }
+               }
+       }
+       return allEvents, networkEvents, nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/container/qemu_runner.go 
new/melange-0.48.0/pkg/container/qemu_runner.go
--- old/melange-0.47.0/pkg/container/qemu_runner.go     2026-03-27 
00:06:38.000000000 +0100
+++ new/melange-0.48.0/pkg/container/qemu_runner.go     2026-03-31 
23:10:54.000000000 +0200
@@ -2329,6 +2329,12 @@
                        if err != nil {
                                return err
                        }
+                       // Skip symlinks — extracted APKs may contain broken 
absolute symlinks
+                       // that are not kernel modules (e.g. 
/lib/modules/<ver>/vmlinuz -> /boot/vmlinuz-virt).
+                       // The kernel image is provided separately via 
QEMU_KERNEL_IMAGE.
+                       if d.Type()&fs.ModeSymlink != 0 {
+                               return nil
+                       }
                        archivePath := filepath.Join(archivePrefix, path)
                        if d.IsDir() {
                                moduleRecords = append(moduleRecords, 
cpio.Directory(archivePath, 0o755))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/sca/sca.go 
new/melange-0.48.0/pkg/sca/sca.go
--- old/melange-0.47.0/pkg/sca/sca.go   2026-03-27 00:06:38.000000000 +0100
+++ new/melange-0.48.0/pkg/sca/sca.go   2026-03-31 23:10:54.000000000 +0200
@@ -828,6 +828,50 @@
        return nil
 }
 
+// generatePerlDeps generates a perl dependency for packages which ship
+// Perl modules.
+func generatePerlDeps(ctx context.Context, hdl SCAHandle, generated 
*config.Dependencies, extraLibDirs []string) error {
+       log := clog.FromContext(ctx)
+       log.Infof("scanning for perl modules...")
+
+       fsys, err := hdl.Filesystem()
+       if err != nil {
+               return err
+       }
+
+       perlModuleMatchUsrLib, err := fs.Glob(fsys, "usr/lib/perl[0-9]*/*_perl")
+       if err != nil {
+               return err
+       }
+       perlModuleMatchLib, err := fs.Glob(fsys, "lib/perl[0-9]*/*_perl")
+       if err != nil {
+               return err
+       }
+
+       perlModuleMatch := slices.Clone(perlModuleMatchUsrLib)
+       perlModuleMatch = append(perlModuleMatch, perlModuleMatchLib...)
+       if len(perlModuleMatch) == 0 {
+               return nil
+       }
+
+       // Get the 'perlN' component of the path
+       perlVersion := strings.Split(perlModuleMatch[0], "/")[2]
+       // Remove the 'perl' prefix
+       perlVersion, _ = strings.CutPrefix(perlVersion, "perl")
+       log.Debugf("  perl module depends on Perl version %s", perlVersion)
+
+       // Do not add a Perl dependency if one already exists.
+       if slices.Contains(hdl.BaseDependencies().Runtime, "perl") {
+               log.Warnf("%s: Perl dependency 'perl' already specified, 
consider removing it in favor of SCA-generated dependency", hdl.PackageName())
+               return nil
+       }
+
+       log.Infof("  found perl module, generating perl~%s dependency", 
perlVersion)
+       generated.Runtime = append(generated.Runtime, fmt.Sprintf("perl~%s", 
perlVersion))
+
+       return nil
+}
+
 // generatePythonDeps generates a python-3.X-base dependency for packages 
which ship
 // Python modules.
 func generatePythonDeps(ctx context.Context, hdl SCAHandle, generated 
*config.Dependencies, extraLibDirs []string) error {
@@ -903,10 +947,17 @@
                return err
        }
 
-       rubyGemMatches, err := fs.Glob(fsys, 
"usr/lib/ruby/gems/[0-9]*.[0-9]*.[0.9]*/gems")
+       rubyGemMatchesUsrLib, err := fs.Glob(fsys, 
"usr/lib/ruby/gems/[0-9]*.[0-9]*.[0.9]*/gems")
        if err != nil {
                return err
        }
+       rubyGemMatchesLib, err := fs.Glob(fsys, 
"lib/ruby/gems/[0-9]*.[0-9]*.[0.9]*/gems")
+       if err != nil {
+               return err
+       }
+
+       rubyGemMatches := slices.Clone(rubyGemMatchesUsrLib)
+       rubyGemMatches = append(rubyGemMatches, rubyGemMatchesLib...)
        if len(rubyGemMatches) == 0 {
                return nil
        }
@@ -1134,6 +1185,7 @@
                generateCmdProviders,
                generateDocDeps,
                generatePkgConfigDeps,
+               generatePerlDeps,
                generatePythonDeps,
                generateRubyDeps,
                generateShbangDeps,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/sca/sca_test.go 
new/melange-0.48.0/pkg/sca/sca_test.go
--- old/melange-0.47.0/pkg/sca/sca_test.go      2026-03-27 00:06:38.000000000 
+0100
+++ new/melange-0.48.0/pkg/sca/sca_test.go      2026-03-31 23:10:54.000000000 
+0200
@@ -217,6 +217,25 @@
        }
 }
 
+func TestPerlSca(t *testing.T) {
+       ctx := slogtest.Context(t)
+       // Generated by:
+       // wget https://packages.wolfi.dev/os/x86_64/perl-json-4.11-r0.apk
+       th := handleFromApk(ctx, t, "perl-json-4.11-r0.apk", "perl-json.yaml")
+       defer th.exp.Close()
+
+       got := config.Dependencies{}
+       if err := Analyze(ctx, th, &got); err != nil {
+               t.Fatal(err)
+       }
+
+       want := config.Dependencies{Runtime: []string{"perl~5"}}
+
+       if diff := cmp.Diff(want, got); diff != "" {
+               t.Errorf("Analyze(): (-want, +got):\n%s", diff)
+       }
+}
+
 func TestRubySca(t *testing.T) {
        ctx := slogtest.Context(t)
        // Generated by:
Binary files old/melange-0.47.0/pkg/sca/testdata/perl-json-4.11-r0.apk and 
new/melange-0.48.0/pkg/sca/testdata/perl-json-4.11-r0.apk differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.47.0/pkg/sca/testdata/perl-json.yaml 
new/melange-0.48.0/pkg/sca/testdata/perl-json.yaml
--- old/melange-0.47.0/pkg/sca/testdata/perl-json.yaml  1970-01-01 
01:00:00.000000000 +0100
+++ new/melange-0.48.0/pkg/sca/testdata/perl-json.yaml  2026-03-31 
23:10:54.000000000 +0200
@@ -0,0 +1,91 @@
+package:
+  name: perl-json
+  version: "4.11"
+  epoch: 0 # go/wolfi-rsc/perl-json
+  description: Perl module implementing a JSON encoder/decoder
+  copyright:
+    - license: GPL-1.0-or-later OR Artistic-1.0-Perl
+  resources:
+    memory: 1Gi
+
+environment:
+  contents:
+    packages:
+      - autoconf
+      - automake
+      - build-base
+      - busybox
+      - ca-certificates-bundle
+      - perl
+      - perl-dev
+
+pipeline:
+  - uses: git-checkout
+    with:
+      repository: https://github.com/makamaka/JSON
+      tag: ${{package.version}}
+      expected-commit: 2e2812bb38a143ef2e0a553104c0a58a693f8ca7
+
+  - runs: PERL_MM_USE_DEFAULT=1 perl Makefile.PL INSTALLDIRS=vendor
+
+  - uses: autoconf/make
+
+  - uses: autoconf/make-install
+
+  - uses: strip
+
+subpackages:
+  - name: perl-json-doc
+    pipeline:
+      - uses: split/manpages
+    description: perl-json manpages
+    test:
+      pipeline:
+        - uses: test/docs
+
+test:
+  environment:
+    contents:
+      packages:
+        - perl
+  pipeline:
+    - name: Verify module loads
+      runs: perl -e 'use JSON; print "JSON loaded\n"'
+    - name: Test JSON encoding
+      runs: |
+        perl -e '
+          use JSON;
+          my $json = JSON->new->allow_nonref;
+          my $encoded = $json->encode({key => "value", num => 42});
+          die "encode failed" unless defined $encoded;
+          print "encode ok: $encoded\n";
+        '
+    - name: Test JSON decoding
+      runs: |
+        perl -e '
+          use JSON;
+          my $json = JSON->new->allow_nonref;
+          my $decoded = $json->decode('"'"'{"key":"value","num":42}'"'"');
+          die "decode failed" unless ref($decoded) eq "HASH";
+          die "key mismatch" unless $decoded->{key} eq "value";
+          die "num mismatch" unless $decoded->{num} == 42;
+          print "decode ok\n";
+        '
+    - name: Test encode/decode roundtrip
+      runs: |
+        perl -e '
+          use JSON;
+          my $data = {list => [1, 2, 3], nested => {a => "b"}};
+          my $encoded = encode_json($data);
+          my $decoded = decode_json($encoded);
+          die "roundtrip failed for list" unless ref($decoded->{list}) eq 
"ARRAY";
+          die "roundtrip failed for nested" unless $decoded->{nested}{a} eq 
"b";
+          print "roundtrip ok\n";
+        '
+    - uses: test/tw/ldd-check
+
+update:
+  enabled: true
+  github:
+    identifier: makamaka/JSON
+    use-tag: true

++++++ melange.obsinfo ++++++
--- /var/tmp/diff_new_pack.phWb2k/_old  2026-04-02 17:43:58.538320089 +0200
+++ /var/tmp/diff_new_pack.phWb2k/_new  2026-04-02 17:43:58.542320254 +0200
@@ -1,5 +1,5 @@
 name: melange
-version: 0.47.0
-mtime: 1774566398
-commit: 3fa79327ebf36fb4b3700d6a053b95836e45ccd8
+version: 0.48.0
+mtime: 1774991454
+commit: 54faa684108e9d1e0a716adc3819e105da614bf7
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/melange/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.melange.new.21863/vendor.tar.gz differ: char 133, 
line 1

Reply via email to