Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pypi-attestations for 
openSUSE:Factory checked in at 2026-03-05 17:29:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pypi-attestations (Old)
 and      /work/SRC/openSUSE:Factory/.python-pypi-attestations.new.561 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pypi-attestations"

Thu Mar  5 17:29:56 2026 rev:2 rq:1336681 version:0.0.29

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-pypi-attestations/python-pypi-attestations.changes
        2025-11-09 21:09:44.735771919 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-pypi-attestations.new.561/python-pypi-attestations.changes
       2026-03-05 17:32:09.440957920 +0100
@@ -1,0 +2,8 @@
+Thu Mar  5 08:21:22 UTC 2026 - Dirk Mรผller <[email protected]>
+
+- update to 0.0.29:
+  * Support for verifying Google Cloud attestations has been
+    added to the CLI.
+  * The minimum Python version required is now `3.10`
+
+-------------------------------------------------------------------

Old:
----
  pypi_attestations-0.0.28.tar.gz

New:
----
  pypi_attestations-0.0.29.tar.gz

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

Other differences:
------------------
++++++ python-pypi-attestations.spec ++++++
--- /var/tmp/diff_new_pack.7ffdMS/_old  2026-03-05 17:32:13.805139431 +0100
+++ /var/tmp/diff_new_pack.7ffdMS/_new  2026-03-05 17:32:13.805139431 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pypi-attestations
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %define upname  pypi_attestations
 Name:           python-pypi-attestations
-Version:        0.0.28
+Version:        0.0.29
 Release:        0
 Summary:        A library to convert between Sigstore Bundles and PEP-740 
Attestation objects
 License:        Apache-2.0

++++++ pypi_attestations-0.0.28.tar.gz -> pypi_attestations-0.0.29.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/.github/dependabot.yml 
new/pypi_attestations-0.0.29/.github/dependabot.yml
--- old/pypi_attestations-0.0.28/.github/dependabot.yml 2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/.github/dependabot.yml 2025-12-11 
14:22:58.000000000 +0100
@@ -2,6 +2,8 @@
 
 updates:
   - package-ecosystem: pip
+    cooldown:
+      default-days: 7
     directory: /
     groups:
       python:
@@ -11,6 +13,8 @@
       interval: daily
 
   - package-ecosystem: github-actions
+    cooldown:
+      default-days: 7
     directory: /
     groups:
       actions:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/.github/workflows/docs.yml 
new/pypi_attestations-0.0.29/.github/workflows/docs.yml
--- old/pypi_attestations-0.0.28/.github/workflows/docs.yml     2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/.github/workflows/docs.yml     2025-12-11 
14:22:58.000000000 +0100
@@ -14,11 +14,11 @@
   build:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 
v6.0.0
         with:
           persist-credentials: false
 
-      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 
v6.0.0
+      - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # 
v6.1.0
         with:
           python-version-file: pyproject.toml
           cache: "pip"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/.github/workflows/lint.yml 
new/pypi_attestations-0.0.29/.github/workflows/lint.yml
--- old/pypi_attestations-0.0.28/.github/workflows/lint.yml     2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/.github/workflows/lint.yml     2025-12-11 
14:22:58.000000000 +0100
@@ -15,11 +15,11 @@
   lint:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 
v6.0.0
         with:
           persist-credentials: false
 
-      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 
v6.0.0
+      - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # 
v6.1.0
         with:
           python-version-file: pyproject.toml
           cache: "pip"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/.github/workflows/release.yml 
new/pypi_attestations-0.0.29/.github/workflows/release.yml
--- old/pypi_attestations-0.0.28/.github/workflows/release.yml  2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/.github/workflows/release.yml  2025-12-11 
14:22:58.000000000 +0100
@@ -15,11 +15,11 @@
     name: Build distributions ๐Ÿ“ฆ
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 
v6.0.0
         with:
           persist-credentials: false
 
-      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 
v6.0.0
+      - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # 
v6.1.0
         with:
           python-version-file: pyproject.toml
           cache: "pip"
@@ -32,7 +32,7 @@
         run: python -m build
 
       - name: Upload distributions
-        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 
# v4.6.2
+        uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 
# v5.0.0
         with:
           name: distributions
           path: dist/
@@ -46,7 +46,7 @@
       attestations: write # to persist the attestation files
     steps:
       - name: Download distributions
-        uses: 
actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
+        uses: 
actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
         with:
           name: distributions
           path: dist/
@@ -67,7 +67,7 @@
 
     steps:
       - name: Download distributions
-        uses: 
actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
+        uses: 
actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
         with:
           name: distributions
           path: dist/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/.github/workflows/tests.yml 
new/pypi_attestations-0.0.29/.github/workflows/tests.yml
--- old/pypi_attestations-0.0.28/.github/workflows/tests.yml    2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/.github/workflows/tests.yml    2025-12-11 
14:22:58.000000000 +0100
@@ -18,18 +18,18 @@
     strategy:
       matrix:
         python:
-          - "3.9"
           - "3.10"
           - "3.11"
           - "3.12"
           - "3.13"
+          - "3.14"
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 
v6.0.0
         with:
           persist-credentials: false
 
-      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 
v6.0.0
+      - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # 
v6.1.0
         with:
           python-version: ${{ matrix.python }}
           cache: "pip"
@@ -46,13 +46,13 @@
   test-offline:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 
v6.0.0
         with:
           persist-credentials: false
 
-      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 
v6.0.0
+      - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # 
v6.1.0
         with:
-          python-version: 3.13
+          python-version: 3.14
           cache: "pip"
           cache-dependency-path: pyproject.toml
           allow-prereleases: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/.github/workflows/zizmor.yml 
new/pypi_attestations-0.0.29/.github/workflows/zizmor.yml
--- old/pypi_attestations-0.0.28/.github/workflows/zizmor.yml   1970-01-01 
01:00:00.000000000 +0100
+++ new/pypi_attestations-0.0.29/.github/workflows/zizmor.yml   2025-12-11 
14:22:58.000000000 +0100
@@ -0,0 +1,24 @@
+name: GitHub Actions Security Analysis with zizmor ๐ŸŒˆ
+
+on:
+  push:
+    branches: ["main"]
+  pull_request:
+    branches: ["**"]
+
+permissions: {}
+
+jobs:
+  zizmor:
+    name: Run zizmor ๐ŸŒˆ
+    runs-on: ubuntu-latest
+    permissions:
+      security-events: write
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 
v6.0.0
+        with:
+          persist-credentials: false
+
+      - name: Run zizmor ๐ŸŒˆ
+        uses: 
zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/.gitignore 
new/pypi_attestations-0.0.29/.gitignore
--- old/pypi_attestations-0.0.28/.gitignore     2025-10-16 18:52:55.000000000 
+0200
+++ new/pypi_attestations-0.0.29/.gitignore     2025-12-11 14:22:58.000000000 
+0100
@@ -1,4 +1,5 @@
 env/
+.venv/
 pip-wheel-metadata/
 *.egg-info/
 __pycache__/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/CHANGELOG.md 
new/pypi_attestations-0.0.29/CHANGELOG.md
--- old/pypi_attestations-0.0.28/CHANGELOG.md   2025-10-16 18:52:55.000000000 
+0200
+++ new/pypi_attestations-0.0.29/CHANGELOG.md   2025-12-11 14:22:58.000000000 
+0100
@@ -7,6 +7,16 @@
 
 ## [Unreleased]
 
+## [0.0.29]
+
+### Added
+
+- Support for verifying Google Cloud attestations has been added to the CLI.
+
+### Changed
+
+- The minimum Python version required is now `3.10`
+
 ## [0.0.28]
 
 ### Changed
@@ -327,7 +337,8 @@
 
 - Initial implementation
 
-[Unreleased]: https://github.com/pypi/pypi-attestations/compare/v0.0.28...HEAD
+[Unreleased]: https://github.com/pypi/pypi-attestations/compare/v0.0.29...HEAD
+[0.0.29]: https://github.com/pypi/pypi-attestations/compare/v0.0.28...v0.0.29
 [0.0.28]: https://github.com/pypi/pypi-attestations/compare/v0.0.27...v0.0.28
 [0.0.27]: https://github.com/pypi/pypi-attestations/compare/v0.0.26...v0.0.27
 [0.0.26]: https://github.com/pypi/pypi-attestations/compare/v0.0.25...v0.0.26
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/Makefile 
new/pypi_attestations-0.0.29/Makefile
--- old/pypi_attestations-0.0.28/Makefile       2025-10-16 18:52:55.000000000 
+0200
+++ new/pypi_attestations-0.0.29/Makefile       2025-12-11 14:22:58.000000000 
+0100
@@ -6,7 +6,7 @@
        $(shell find test -name '*.py')
 
 # Optionally overriden by the user, if they're using a virtual environment 
manager.
-VENV ?= env
+VENV ?= .venv
 
 # On Windows, venv scripts/shims are under `Scripts` instead of `bin`.
 VENV_BIN := $(VENV)/bin
@@ -45,7 +45,7 @@
 
 $(VENV)/pyvenv.cfg: pyproject.toml
        # Create our Python 3 virtual environment
-       python3 -m venv env
+       python3 -m venv $(VENV)
        $(VENV_BIN)/python -m pip install --upgrade pip
        $(VENV_BIN)/python -m pip install -e .[$(INSTALL_EXTRA)]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/PKG-INFO 
new/pypi_attestations-0.0.29/PKG-INFO
--- old/pypi_attestations-0.0.28/PKG-INFO       2025-10-16 18:53:04.782124000 
+0200
+++ new/pypi_attestations-0.0.29/PKG-INFO       2025-12-11 14:23:05.831881500 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: pypi-attestations
-Version: 0.0.28
+Version: 0.0.29
 Summary: A library to convert between Sigstore Bundles and PEP-740 Attestation 
objects
 Author-email: Trail of Bits <[email protected]>
 Maintainer-email: PyPI Admins <[email protected]>
@@ -10,7 +10,7 @@
 Project-URL: Issues, https://github.com/pypi/pypi-attestations/issues
 Project-URL: Source, https://github.com/pypi/pypi-attestations
 Classifier: Programming Language :: Python :: 3
-Requires-Python: >=3.9
+Requires-Python: >=3.10
 Description-Content-Type: text/markdown
 License-File: LICENSE
 Requires-Dist: cryptography
@@ -187,10 +187,6 @@
 
 ### Verifying a PyPI package
 
-> [!IMPORTANT]
-> This subcommand supports publish attestations from GitHub and GitLab.
-> It **does not currently support** Google Cloud-based publish attestations.
-
 > [!NOTE]
 > The package to verify can be passed either as a path to a local file, a
 > `pypi:` prefixed filename (e.g: 'pypi:sampleproject-1.0.0-py3-none-any.whl'),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/README.md 
new/pypi_attestations-0.0.29/README.md
--- old/pypi_attestations-0.0.28/README.md      2025-10-16 18:52:55.000000000 
+0200
+++ new/pypi_attestations-0.0.29/README.md      2025-12-11 14:22:58.000000000 
+0100
@@ -144,10 +144,6 @@
 
 ### Verifying a PyPI package
 
-> [!IMPORTANT]
-> This subcommand supports publish attestations from GitHub and GitLab.
-> It **does not currently support** Google Cloud-based publish attestations.
-
 > [!NOTE]
 > The package to verify can be passed either as a path to a local file, a
 > `pypi:` prefixed filename (e.g: 'pypi:sampleproject-1.0.0-py3-none-any.whl'),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/pyproject.toml 
new/pypi_attestations-0.0.29/pyproject.toml
--- old/pypi_attestations-0.0.28/pyproject.toml 2025-10-16 18:52:55.000000000 
+0200
+++ new/pypi_attestations-0.0.29/pyproject.toml 2025-12-11 14:22:58.000000000 
+0100
@@ -22,7 +22,7 @@
     "sigstore >= 4.0, < 5.0",
     "sigstore-models",
 ]
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 
 [tool.setuptools.dynamic]
 version = { attr = "pypi_attestations.__version__" }
@@ -64,7 +64,7 @@
 mypy_path = "src"
 packages = "pypi_attestations"
 plugins = ["pydantic.mypy"]
-python_version = "3.9"
+python_version = "3.10"
 allow_redefinition = true
 check_untyped_defs = true
 disallow_incomplete_defs = true
@@ -83,7 +83,7 @@
 
 [tool.ruff]
 line-length = 100
-target-version = "py39"
+target-version = "py310"
 
 [tool.ruff.lint]
 select = ["E", "F", "I", "W", "UP", "ANN", "D", "COM", "ISC", "TCH", "SLF"]
@@ -91,9 +91,6 @@
 # COM812 and ISC001 can cause conflicts when using ruff as a formatter.
 # See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules.
 ignore = ["D203", "D213", "COM812", "ISC001"]
-# Needed since Pydantic relies on runtime type annotations, and we target 
Python versions
-# < 3.10. See 
https://docs.astral.sh/ruff/rules/non-pep604-annotation/#why-is-this-bad
-pyupgrade.keep-runtime-typing = true
 
 [tool.ruff.lint.per-file-ignores]
 
@@ -107,6 +104,6 @@
 [tool.interrogate]
 # don't enforce documentation coverage for packaging, testing, the virtual
 # environment, or the CLI (which is documented separately).
-exclude = ["env", "test", "src/pypi_attestations/__main__.py"]
+exclude = [".venv", "test", "src/pypi_attestations/__main__.py"]
 ignore-semiprivate = true
 fail-under = 100
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/src/pypi_attestations/__init__.py 
new/pypi_attestations-0.0.29/src/pypi_attestations/__init__.py
--- old/pypi_attestations-0.0.28/src/pypi_attestations/__init__.py      
2025-10-16 18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/src/pypi_attestations/__init__.py      
2025-12-11 14:22:58.000000000 +0100
@@ -1,6 +1,6 @@
 """The `pypi-attestations` APIs."""
 
-__version__ = "0.0.28"
+__version__ = "0.0.29"
 
 from ._impl import (
     Attestation,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/src/pypi_attestations/_cli.py 
new/pypi_attestations-0.0.29/src/pypi_attestations/_cli.py
--- old/pypi_attestations-0.0.28/src/pypi_attestations/_cli.py  2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/src/pypi_attestations/_cli.py  2025-12-11 
14:22:58.000000000 +0100
@@ -156,11 +156,16 @@
     verify_pypi_command.add_argument(
         "--repository",
         type=str,
-        required=True,
         help="URL of the publishing GitHub or GitLab repository",
     )
 
     verify_pypi_command.add_argument(
+        "--gcp-service-account",
+        type=str,
+        help="Email of the Google Cloud service account",
+    )
+
+    verify_pypi_command.add_argument(
         "--staging",
         action="store_true",
         default=False,
@@ -576,9 +581,27 @@
     try:
         for attestation_bundle in provenance.attestation_bundles:
             publisher = attestation_bundle.publisher
-            if isinstance(publisher, GooglePublisher):  # pragma: no cover
-                _die("This CLI doesn't support Google Cloud-based publisher 
verification")
-            
_check_repository_identity(expected_repository_url=args.repository, 
publisher=publisher)
+            if isinstance(publisher, GooglePublisher):
+                if not args.gcp_service_account:
+                    _die(
+                        "Provenance signed by a Google Cloud account, but no 
service account "
+                        "provided; use '--gcp-service-account'"
+                    )
+                if publisher.email != args.gcp_service_account:
+                    _die(
+                        f"Verification failed: provenance was signed by 
service account "
+                        f'"{publisher.email}", expected 
"{args.gcp_service_account}"'
+                    )
+            else:
+                if not args.repository:
+                    _die(
+                        "Provenance signed by a repository, but no repository 
URL provided; "
+                        "use '--repository'"
+                    )
+                _check_repository_identity(
+                    expected_repository_url=args.repository, 
publisher=publisher
+                )
+
             policy = publisher._as_policy()  # noqa: SLF001
             for attestation in attestation_bundle.attestations:
                 attestation.verify(policy, dist, staging=args.staging, 
offline=args.offline)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/src/pypi_attestations/_impl.py 
new/pypi_attestations-0.0.29/src/pypi_attestations/_impl.py
--- old/pypi_attestations-0.0.28/src/pypi_attestations/_impl.py 2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/src/pypi_attestations/_impl.py 2025-12-11 
14:22:58.000000000 +0100
@@ -8,7 +8,7 @@
 import base64
 import json
 from enum import Enum
-from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType, Optional, 
Union, get_args
+from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType
 
 import packaging
 import packaging.tags
@@ -238,7 +238,7 @@
         *,
         staging: bool = False,
         offline: bool = False,
-    ) -> tuple[str, Optional[dict[str, Any]]]:
+    ) -> tuple[str, dict[str, Any] | None]:
         """Verify against an existing Python distribution.
 
         The `identity` can be an object confirming to
@@ -257,8 +257,7 @@
         # NOTE: Can't do `isinstance` with `Publisher` since it's
         # a `_GenericAlias`; instead we punch through to the inner
         # `_Publisher` union.
-        # Use of typing.get_args is needed for Python < 3.10
-        if isinstance(identity, get_args(_Publisher)):
+        if isinstance(identity, _Publisher):
             policy = identity._as_policy()  # noqa: SLF001
         else:
             policy = identity
@@ -412,7 +411,7 @@
     packaging.utils.BuildTag,
     frozenset[packaging.tags.Tag],
 ]
-_DistName = Union[_SdistName, _BdistName]
+_DistName = _SdistName | _BdistName
 
 
 def _check_dist_filename(dist: str) -> _DistName:
@@ -544,7 +543,7 @@
     action.
     """
 
-    environment: Optional[str] = None
+    environment: str | None = None
     """
     The optional name GitHub Actions environment that the publishing
     action was performed from.
@@ -637,7 +636,7 @@
     but can be customized.
     """
 
-    environment: Optional[str] = None
+    environment: str | None = None
     """
     The optional environment that the publishing action was performed from.
     """
@@ -661,7 +660,7 @@
         return policy.Identity(identity=self.email, 
issuer="https://accounts.google.com";)
 
 
-_Publisher = Union[GitHubPublisher, GitLabPublisher, GooglePublisher]
+_Publisher = GitHubPublisher | GitLabPublisher | GooglePublisher
 Publisher = Annotated[_Publisher, Field(discriminator="kind")]
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/src/pypi_attestations.egg-info/PKG-INFO 
new/pypi_attestations-0.0.29/src/pypi_attestations.egg-info/PKG-INFO
--- old/pypi_attestations-0.0.28/src/pypi_attestations.egg-info/PKG-INFO        
2025-10-16 18:53:04.000000000 +0200
+++ new/pypi_attestations-0.0.29/src/pypi_attestations.egg-info/PKG-INFO        
2025-12-11 14:23:05.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: pypi-attestations
-Version: 0.0.28
+Version: 0.0.29
 Summary: A library to convert between Sigstore Bundles and PEP-740 Attestation 
objects
 Author-email: Trail of Bits <[email protected]>
 Maintainer-email: PyPI Admins <[email protected]>
@@ -10,7 +10,7 @@
 Project-URL: Issues, https://github.com/pypi/pypi-attestations/issues
 Project-URL: Source, https://github.com/pypi/pypi-attestations
 Classifier: Programming Language :: Python :: 3
-Requires-Python: >=3.9
+Requires-Python: >=3.10
 Description-Content-Type: text/markdown
 License-File: LICENSE
 Requires-Dist: cryptography
@@ -187,10 +187,6 @@
 
 ### Verifying a PyPI package
 
-> [!IMPORTANT]
-> This subcommand supports publish attestations from GitHub and GitLab.
-> It **does not currently support** Google Cloud-based publish attestations.
-
 > [!NOTE]
 > The package to verify can be passed either as a path to a local file, a
 > `pypi:` prefixed filename (e.g: 'pypi:sampleproject-1.0.0-py3-none-any.whl'),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/src/pypi_attestations.egg-info/SOURCES.txt 
new/pypi_attestations-0.0.29/src/pypi_attestations.egg-info/SOURCES.txt
--- old/pypi_attestations-0.0.28/src/pypi_attestations.egg-info/SOURCES.txt     
2025-10-16 18:53:04.000000000 +0200
+++ new/pypi_attestations-0.0.29/src/pypi_attestations.egg-info/SOURCES.txt     
2025-12-11 14:23:05.000000000 +0100
@@ -11,6 +11,7 @@
 .github/workflows/lint.yml
 .github/workflows/release.yml
 .github/workflows/tests.yml
+.github/workflows/zizmor.yml
 src/pypi_attestations/__init__.py
 src/pypi_attestations/__main__.py
 src/pypi_attestations/_cli.py
@@ -28,6 +29,8 @@
 test/test_impl.py
 test/test_init.py
 test/assets/200170367.pem
+test/assets/gcb_attestation_test-0.0.0.tar.gz
+test/assets/gcb_attestation_test-0.0.0.tar.gz.provenance
 test/assets/gitlab_oidc_project-0.0.3.tar.gz
 test/assets/gitlab_oidc_project-0.0.3.tar.gz.publish.attestation
 test/assets/no-source-repository-ref-extension.pem
Binary files 
old/pypi_attestations-0.0.28/test/assets/gcb_attestation_test-0.0.0.tar.gz and 
new/pypi_attestations-0.0.29/test/assets/gcb_attestation_test-0.0.0.tar.gz 
differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pypi_attestations-0.0.28/test/assets/gcb_attestation_test-0.0.0.tar.gz.provenance
 
new/pypi_attestations-0.0.29/test/assets/gcb_attestation_test-0.0.0.tar.gz.provenance
--- 
old/pypi_attestations-0.0.28/test/assets/gcb_attestation_test-0.0.0.tar.gz.provenance
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/pypi_attestations-0.0.29/test/assets/gcb_attestation_test-0.0.0.tar.gz.provenance
       2025-12-11 14:22:58.000000000 +0100
@@ -0,0 +1 @@
+{"attestation_bundles":[{"attestations":[{"envelope":{"signature":"MEUCIQDvmv1O9ioY84yUE79+ckD+AF3uHi+twKtJRfUlyYgCyQIgRDcFfWof5Gf4lvJzkTKGtNaJ9NlCHF+BA3yUwRKNaTw=","statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2NiX2F0dGVzdGF0aW9uX3Rlc3QtMC4wLjAudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjQ2MTMxNzM2MjQxOTEyNGI2MDEyZTg1NTQyM2E5MDc4ZDZkZThhZWQzZTc0ZmE3OGNjNzRkNjY5YjIzZGM2Y2YifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9kb2NzLnB5cGkub3JnL2F0dGVzdGF0aW9ucy9wdWJsaXNoL3YxIiwicHJlZGljYXRlIjpudWxsfQ=="},"verification_material":{"certificate":"MIIC7DCCAnKgAwIBAgIUVJkn21utBSU3vjewVuPQb3e8Jz0wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDIxMTUwMjI3WhcNMjUwNDIxMTUxMjI3WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE16HRcqztt38BoUOwhhagqdU43mBPeR9sctF0jTQ00NUpjWqvPc8CMmKR85kpwFxS2WfPe7D0wIByY8ZfdgT/66OCAZEwggGNMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUi5A/s39XjLixRjkQs8mHtSEpTFMwHwYDVR0j
 
BBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wQAYDVR0RAQH/BDYwNIEyOTE5NDM2MTU4MjM2LWNvbXB1dGVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsGCisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGLBgorBgEEAdZ5AgQCBH0EewB5AHcA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGWWN88EgAABAMASDBGAiEA9EUW3yTYEtEeZ0SMaYlHPZ2+LHrae1hb+9bCRmdMjgwCIQDSMxXrTejGcgOZqJT8jxCZT77yieMU16PO92ZrpQ5wrjAKBggqhkjOPQQDAwNoADBlAjEAxl/X0fmqgftikX/Lq+c++syGCCNf1zHB35VYPSqN+vZvLEzbASrJjx6fFMID8pF4AjBXeTTem553VCEM3Y9bMuM9eSen6by5XyGTWL0j7ro/YjmSC+xs9IHoSHQ6vYRQH00=","transparency_entries":[{"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMGMxNzdhODg2MDZmNWE0Yjg1ZmZjZjE3YjAwOTI3NzI1MjNhOGZkZjBjZjdmNTFlY2UxN2VhMTk1ZjU2NzQxOCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6Ijc2ODhiNDgzNGYwM2U4YTRlNmNhZWVmOTc2NWQyYzQzNzk5ZGNhN2U1ODNjZTJhODc1MDkwNDBjODgzZjhlYmUifSwic
 
2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRRHZtdjFPOWlvWTg0eVVFNzkrY2tEK0FGM3VIaSt0d0t0SlJmVWx5WWdDeVFJZ1JEY0ZmV29mNUdmNGx2SnprVEtHdE5hSjlObENIRitCQTN5VXdSS05hVHc9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNM1JFTkRRVzVMWjBGM1NVSkJaMGxWVmtwcmJqSXhkWFJDVTFVemRtcGxkMVoxVUZGaU0yVTRTbm93ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVTWGhOVkZWM1RXcEpNMWRvWTA1TmFsVjNUa1JKZUUxVVZYaE5ha2t6VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVXhOa2hTWTNGNmRIUXpPRUp2VlU5M2FHaGhaM0ZrVlRRemJVSlFaVkk1YzJOMFJqQUthbFJSTURCT1ZYQnFWM0YyVUdNNFEwMXRTMUk0Tld0d2QwWjRVekpYWmxCbE4wUXdkMGxDZVZrNFdtWmtaMVF2TmpaUFEwRmFSWGRuWjBkT1RVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVnBOVUV2Q25Nek9WaHFUR2w0VW1wclVYTTRiVWgwVTBWd1ZFWk5kMGgzV1VSV1VqQnFRa0puZDBadlFWVX
 
pPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMUZCV1VSV1VqQlNRVkZJTDBKRVdYZE9TVVY1VDFSRk5VNUVUVEpOVkZVMFRXcE5Na3hYVG5aaVdFSXhaRWRXUVZwSFZqSmFWM2gyWTBkV2VRcE1iV1I2V2xoS01tRlhUbXhaVjA1cVlqTldkV1JETldwaU1qQjNTMUZaUzB0M1dVSkNRVWRFZG5wQlFrRlJVV0poU0ZJd1kwaE5Oa3g1T1doWk1rNTJDbVJYTlRCamVUVnVZakk1Ym1KSFZYVlpNamwwVFVOelIwTnBjMGRCVVZGQ1p6YzRkMEZSWjBWSVVYZGlZVWhTTUdOSVRUWk1lVGxvV1RKT2RtUlhOVEFLWTNrMWJtSXlPVzVpUjFWMVdUSTVkRTFKUjB4Q1oyOXlRbWRGUlVGa1dqVkJaMUZEUWtnd1JXVjNRalZCU0dOQk0xUXdkMkZ6WWtoRlZFcHFSMUkwWXdwdFYyTXpRWEZLUzFoeWFtVlFTek12YURSd2VXZERPSEEzYnpSQlFVRkhWMWRPT0RoRlowRkJRa0ZOUVZORVFrZEJhVVZCT1VWVlZ6TjVWRmxGZEVWbENsb3dVMDFoV1d4SVVGb3lLMHhJY21GbE1XaGlLemxpUTFKdFpFMXFaM2REU1ZGRVUwMTRXSEpVWldwSFkyZFBXbkZLVkRocWVFTmFWRGMzZVdsbFRWVUtNVFpRVHpreVduSndVVFYzY21wQlMwSm5aM0ZvYTJwUFVGRlJSRUYzVG05QlJFSnNRV3BGUVhoc0wxZ3dabTF4WjJaMGFXdFlMMHh4SzJNckszTjVSd3BEUTA1bU1YcElRak0xVmxsUVUzRk9LM1phZGt4RmVtSkJVM0pLYW5nMlprWk5TVVE0Y0VZMFFXcENXR1ZVVkdWdE5UVXpWa05GVFROWk9XSk5kVTA1Q21WVFpXNDJZbmsxV0hsSFZGZE1NR28
 
zY204dldXcHRVME1yZUhNNVNVaHZVMGhSTm5aWlVsRklNREE5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn1dfX0=","inclusionPromise":{"signedEntryTimestamp":"MEUCIQD7SQTmBd5ANk107TOcsarnbywnOM8GvPiFjsnd3XE8iQIgGSUUbpi8Qi4Oh/+hmOT9JQhsWpLFJd7l6DKwZeSWwMs="},"inclusionProof":{"checkpoint":{"envelope":"rekor.sigstore.dev
 - 
1193050959916656506\n78266107\nyTVQR2Socsw5nyWnvnYLqwOxBQRXiOZORK1cgJ6y7qI=\n\nโ€”
 rekor.sigstore.dev 
wNI9ajBFAiEA7otHsrHxWEz4Cl6rzVxxwPutBhHfcvOuRrUTM5srjUYCIF10wsHxGBfjflyR5QTTYl6Wcj9GpAMsN2uHkANKgRGu\n"},"hashes":["KVLYepdrDI5/LfHdWT1V7PN/MotHRCtfRperGwlO9JA=","lMH6ki0y7U5Of2brFmlIYF03HuN88eFy6T8eWlyB/dk=","4O+gSq+awPls7N18oHMxdt/NTTOAOViQy6fQWvYR4FU=","UdNOa+z8HXYyMRlPTrMP6f0u2SuYAycU9osH2XmfOFE=","fKLixv2KO9tAJNy/Y7Jx9jp4xsOmz/iWHfGo6h/tQfY=","iiMUkc1juY2OYURyjaGDN4yeGwK+vlahZgBqGwaoxxk=","Qs31aP8k4DnnQFAbKPqTVyMQnPITYnouZgayf6eVuH0=","Gj0wra8n0qTMwIaUpL2oKbJ5hRKqfEBMeGWQ0znz+Uc=","12yDkzWUtOuQpFBmsOEepvEQN6k8pfUARwzfDsqE5o4=","H6YN+nyPx48sKudbYXqp6lrbloDk93pvBQXfe01rp
 
aQ=","CkAnGwVEwNrYv/Y6Lbm9Q/XPKBSzJh4VT7/YI1PU48Y=","nDWqvIPJey+hFoi5jU9xvVb382cPBtUxKz54B39oXfE=","3NymYFL9lsZznt4xbSrh8IjEcFrJYRYBn1Elo+ASpeA=","2c0Z0VGteoqr0adlQJB2QTdZT3Cn912kMtCAGoE/xW0=","PHJDSL8Ui2OWQsJZ4vZa/V48UosV5lnRgMOoVTBsbDw=","gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=","7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="],"logIndex":"78266105","rootHash":"yTVQR2Socsw5nyWnvnYLqwOxBQRXiOZORK1cgJ6y7qI=","treeSize":"78266107"},"integratedTime":"1745247747","kindVersion":{"kind":"dsse","version":"0.0.1"},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"logIndex":"200170367"}]},"version":1}],"publisher":{"email":"[email protected]","kind":"Google"}}],"version":1}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/test/test_cli.py 
new/pypi_attestations-0.0.29/test/test_cli.py
--- old/pypi_attestations-0.0.28/test/test_cli.py       2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/test/test_cli.py       2025-12-11 
14:22:58.000000000 +0100
@@ -50,6 +50,12 @@
 converted_sigstore_bundle_path = _ASSETS / 
"pypi_attestation_models-0.0.4a2.tar.gz.attestation"
 
 
+# gcb assets
+gcb_artifact_path = _ASSETS / "gcb_attestation_test-0.0.0.tar.gz"
+gcb_provenance_path = _ASSETS / "gcb_attestation_test-0.0.0.tar.gz.provenance"
+gcb_service_account = "[email protected]"
+
+
 def run_main_with_command(cmd: list[str]) -> None:
     """Helper method to run the main function with a given command."""
     sys.argv[1:] = cmd
@@ -939,3 +945,70 @@
             )
 
     assert "Output file already exists" in caplog.text
+
+
+def test_verify_pypi_command_gcb_ok(caplog: pytest.LogCaptureFixture) -> None:
+    run_main_with_command(
+        [
+            "verify",
+            "pypi",
+            "--offline",
+            "--gcp-service-account",
+            gcb_service_account,
+            "--provenance-file",
+            gcb_provenance_path.as_posix(),
+            gcb_artifact_path.as_posix(),
+        ]
+    )
+    assert "OK: gcb_attestation_test-0.0.0.tar.gz" in caplog.text
+
+
+def test_verify_pypi_command_gcb_no_service_account(caplog: 
pytest.LogCaptureFixture) -> None:
+    with pytest.raises(SystemExit):
+        run_main_with_command(
+            [
+                "verify",
+                "pypi",
+                "--offline",
+                "--provenance-file",
+                gcb_provenance_path.as_posix(),
+                gcb_artifact_path.as_posix(),
+            ]
+        )
+    assert (
+        "Provenance signed by a Google Cloud account, but no service account 
provided"
+        in caplog.text
+    )
+
+
+def test_verify_pypi_command_gcb_wrong_service_account(caplog: 
pytest.LogCaptureFixture) -> None:
+    with pytest.raises(SystemExit):
+        run_main_with_command(
+            [
+                "verify",
+                "pypi",
+                "--offline",
+                "--gcp-service-account",
+                "[email protected]",
+                "--provenance-file",
+                gcb_provenance_path.as_posix(),
+                gcb_artifact_path.as_posix(),
+            ]
+        )
+    assert "Verification failed: provenance was signed by service account" in 
caplog.text
+
+
+def test_verify_pypi_command_github_no_repository(caplog: 
pytest.LogCaptureFixture) -> None:
+    # Use a github-signed attestation to check the other path
+    with pytest.raises(SystemExit):
+        run_main_with_command(
+            [
+                "verify",
+                "pypi",
+                "--offline",
+                "--provenance-file",
+                pypi_sdist_provenance_path.as_posix(),
+                pypi_sdist_path.as_posix(),
+            ]
+        )
+    assert "Provenance signed by a repository, but no repository URL provided" 
in caplog.text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pypi_attestations-0.0.28/test/test_impl.py 
new/pypi_attestations-0.0.29/test/test_impl.py
--- old/pypi_attestations-0.0.28/test/test_impl.py      2025-10-16 
18:52:55.000000000 +0200
+++ new/pypi_attestations-0.0.29/test/test_impl.py      2025-12-11 
14:22:58.000000000 +0100
@@ -46,6 +46,10 @@
 gl_signed_dist = impl.Distribution.from_file(gl_signed_dist_path)
 gl_attestation_path = _ASSETS / 
"gitlab_oidc_project-0.0.3.tar.gz.publish.attestation"
 
+gcb_signed_dist_path = _ASSETS / "gcb_attestation_test-0.0.0.tar.gz"
+gcb_signed_dist = impl.Distribution.from_file(gcb_signed_dist_path)
+gcb_attestation_path = _ASSETS / "gcb_attestation_test-0.0.0.tar.gz.provenance"
+
 
 class TestDistribution:
     def test_from_file_nonexistent(self, tmp_path: Path) -> None:
@@ -199,6 +203,17 @@
         with pytest.raises(impl.VerificationError, match=r"Build Config URI .+ 
does not match"):
             attestation.verify(publisher, gl_signed_dist, offline=True)
 
+    def test_verify_from_google_publisher(self) -> None:
+        publisher = impl.GooglePublisher(
+            email="[email protected]",
+        )
+
+        provenance = 
impl.Provenance.model_validate_json(gcb_attestation_path.read_bytes())
+        attestation = provenance.attestation_bundles[0].attestations[0]
+        predicate_type, predicate = attestation.verify(publisher, 
gcb_signed_dist, offline=True)
+        assert predicate_type == 
"https://docs.pypi.org/attestations/publish/v1";
+        assert predicate is None
+
     def test_verify(self) -> None:
         # Our checked-in asset has this identity.
         pol = policy.Identity(

Reply via email to