From 3bb230a4427c353b4aa59fa00df1b5e6a228edc4 Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Mon, 12 Oct 2026 22:38:54 -0400 Subject: [PATCH 1/3] Add reproducible source evidence for released core wheel --- .github/workflows/wheel-provenance.yml | 98 +++++++++++++++++ MANIFEST.in | 3 +- README.md | 1 + docs/WHEEL-PROVENANCE.md | 61 +++++++++++ docs/releases/0.1.0a1-wheel.json | 19 ++++ scripts/reproduce-release-wheel.py | 142 +++++++++++++++++++++++++ tests/test_release_wheel_provenance.py | 78 ++++++++++++++ 7 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/wheel-provenance.yml create mode 100644 docs/WHEEL-PROVENANCE.md create mode 100644 docs/releases/0.1.0a1-wheel.json create mode 100755 scripts/reproduce-release-wheel.py create mode 100644 tests/test_release_wheel_provenance.py diff --git a/.github/workflows/wheel-provenance.yml b/.github/workflows/wheel-provenance.yml new file mode 100644 index 0000000..39c2510 --- /dev/null +++ b/.github/workflows/wheel-provenance.yml @@ -0,0 +1,98 @@ +name: Release wheel source evidence + +on: + pull_request: + paths: + - .github/workflows/wheel-provenance.yml + - scripts/reproduce-release-wheel.py + - tests/test_release_wheel_provenance.py + - docs/releases/0.1.0a1-wheel.json + - docs/WHEEL-PROVENANCE.md + push: + branches: [main] + paths: + - .github/workflows/wheel-provenance.yml + - scripts/reproduce-release-wheel.py + - tests/test_release_wheel_provenance.py + - docs/releases/0.1.0a1-wheel.json + - docs/WHEEL-PROVENANCE.md + workflow_dispatch: + +permissions: + contents: read + +jobs: + reproduce: + name: Reproduce the exact released core wheel + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: tooling + persist-credentials: false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Univeracity/limitlesslibrary + ref: b9249e8de3e2cf558b70719dcd6e7a2ec44dd352 + path: release-source + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + - name: Install exact build tools + run: python -m pip install pip==26.2.1 build==1.5.0 setuptools==83.0.0 wheel==0.48.0 + - name: Rebuild twice and compare all delivered package files + run: | + python tooling/scripts/reproduce-release-wheel.py \ + --source-root release-source \ + --recipe tooling/docs/releases/0.1.0a1-wheel.json \ + --output proof + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: core-wheel-reproduction + path: proof/* + if-no-files-found: error + retention-days: 90 + + attest: + name: Sign and verify the exact source-to-wheel evidence + needs: reproduce + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: core-wheel-reproduction + path: proof + - name: Attest the rebuilt wheel with its exact source and build recipe + id: attest + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 + with: + subject-path: proof/limitless_library-0.1.0a1-py3-none-any.whl + predicate-type: https://github.com/Univeracity/limitlesslibrary/releases/wheel-build/v1 + predicate-path: proof/wheel-build.json + - name: Verify the signed evidence with GitHub CLI + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: | + gh attestation verify proof/limitless_library-0.1.0a1-py3-none-any.whl \ + --repo "$GH_REPO" \ + --signer-workflow "$GH_REPO/.github/workflows/wheel-provenance.yml" \ + --predicate-type https://github.com/Univeracity/limitlesslibrary/releases/wheel-build/v1 \ + --format json > proof/verification.json + cp "$ATTESTATION_BUNDLE" proof/wheel-build.sigstore.json + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: attested-core-wheel + path: proof/* + if-no-files-found: error + retention-days: 90 diff --git a/MANIFEST.in b/MANIFEST.in index 7183506..f0e3bad 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,4 +6,5 @@ include src/limitless_library/py.typed recursive-include examples *.json *.py *.md recursive-include docs *.md *.png recursive-include docs *.svg -include scripts/limitless scripts/verify-source-quickstart.py +include scripts/limitless scripts/verify-source-quickstart.py scripts/reproduce-release-wheel.py +recursive-include docs/releases *.json diff --git a/README.md b/README.md index 84c1b5e..7be05d6 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,7 @@ Outputs are immutable: both commands refuse to overwrite an existing path. | [Managed-service connector](docs/MANAGED-SERVICE.md) | Explicit endpoint, trust, policy, and query boundary | | [Trust model](docs/TRUST-MODEL.md) | Security boundaries and failure behavior | | [Conformance](docs/CONFORMANCE.md) | Required outcomes and fixture expectations | +| [Release wheel source evidence](docs/WHEEL-PROVENANCE.md) | Reproduce and authenticate the distributed core wheel | | [Open-source foundation](docs/OPEN-SOURCE-FOUNDATION.md) | Relationship between the open implementation and product | | [Examples](examples/README.md) | Primitive-by-primitive walkthrough | diff --git a/docs/WHEEL-PROVENANCE.md b/docs/WHEEL-PROVENANCE.md new file mode 100644 index 0000000..c8ad87c --- /dev/null +++ b/docs/WHEEL-PROVENANCE.md @@ -0,0 +1,61 @@ +# Public core wheel source evidence + +The `0.1.0a1` core wheel distributed with the Omarchy plugin is a pure-Python +package. Its exact SHA-256 is +`1f776299c684e1b52430585b640917a37ca88eac9ef0db0414ad0e18e8094986`. +Its package sources are public at commit +[`b9249e8de3e2cf558b70719dcd6e7a2ec44dd352`](https://github.com/Univeracity/limitlesslibrary/tree/b9249e8de3e2cf558b70719dcd6e7a2ec44dd352/src/limitless_library). + +[The build recipe](releases/0.1.0a1-wheel.json) records the source commit, +its timestamp, the exact wheel digest, and the pinned build tools. +The [source evidence workflow](../.github/workflows/wheel-provenance.yml): + +1. Checks out that exact public source commit and verifies its identity. +2. Builds two fresh Git archives with the pinned tools and the commit timestamp + as `SOURCE_DATE_EPOCH`. +3. Requires both complete wheel digests to match the wheel already distributed. + This covers generated metadata and entry points as well as package code. +4. Compares every package file against the source archive, including Python, + schemas, conformance resources, and `py.typed`; the report records their hashes. +5. On `main`, signs a statement binding that wheel digest to the explicit source + commit, tree, build tools, two rebuild results, and public workflow run. + A separate job verifies the signature before publishing the evidence artifact. + +This is a new reproduction of the existing wheel. It does not claim to be an +attestation recorded during its original release build or a security audit. +The signing workflow's own commit is separately recorded from the historical +source commit used to build the wheel. + +## Verify the distributed wheel + +Download the wheel from the +[`v0.1.0a1` release](https://github.com/Univeracity/limitlesslibrary/releases/tag/v0.1.0a1). +Using a current GitHub CLI: + +```sh +gh attestation verify limitless_library-0.1.0a1-py3-none-any.whl \ + --repo Univeracity/limitlesslibrary \ + --signer-workflow Univeracity/limitlesslibrary/.github/workflows/wheel-provenance.yml \ + --predicate-type https://github.com/Univeracity/limitlesslibrary/releases/wheel-build/v1 \ + --format json +``` + +Inspect the verified statement's source commit and wheel digest against the +plugin's `runtime/bundle.json`. The predicate type above identifies the version 1 +JSON build report emitted by `scripts/reproduce-release-wheel.py`. +Successful runs retain the rebuilt wheel, report, Sigstore bundle, and verification +result as the `attested-core-wheel` artifact. Published release evidence bundles +can also be verified offline with GitHub CLI's `--bundle` option. + +## Reproduce locally + +Use Python 3.12 with `pip==26.2.1`, `build==1.5.0`, `setuptools==83.0.0`, and +`wheel==0.48.0`. Keep the build tooling checkout separate from a detached checkout +of the historical source commit, then run: + +```sh +python scripts/reproduce-release-wheel.py \ + --source-root /path/to/historical-source \ + --recipe docs/releases/0.1.0a1-wheel.json \ + --output /path/to/reproduction-output +``` diff --git a/docs/releases/0.1.0a1-wheel.json b/docs/releases/0.1.0a1-wheel.json new file mode 100644 index 0000000..056bc22 --- /dev/null +++ b/docs/releases/0.1.0a1-wheel.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": "limitless.release-wheel-build/1", + "source": { + "repository": "https://github.com/Univeracity/limitlesslibrary", + "commit": "b9249e8de3e2cf558b70719dcd6e7a2ec44dd352", + "sourceDateEpoch": 1791523652 + }, + "wheel": { + "name": "limitless_library-0.1.0a1-py3-none-any.whl", + "sha256": "1f776299c684e1b52430585b640917a37ca88eac9ef0db0414ad0e18e8094986" + }, + "tools": { + "python": "3.12", + "pip": "26.2.1", + "build": "1.5.0", + "setuptools": "83.0.0", + "wheel": "0.48.0" + } +} diff --git a/scripts/reproduce-release-wheel.py b/scripts/reproduce-release-wheel.py new file mode 100755 index 0000000..3cf9368 --- /dev/null +++ b/scripts/reproduce-release-wheel.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Rebuild an exact public release wheel twice and record its source correspondence.""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.metadata +import io +import json +import os +import platform +import re +import shutil +import subprocess +import sys +import tarfile +import tempfile +import zipfile +from pathlib import Path + + +def git(root: Path, *arguments: str) -> bytes: + return subprocess.check_output(["git", "-C", str(root), *arguments]) + + +def source_archive(root: Path, source: dict[str, object]) -> tuple[bytes, str]: + commit = source["commit"] + if not isinstance(commit, str) or not re.fullmatch(r"[0-9a-f]{40}", commit): + raise ValueError("source commit must be a full Git SHA") + if git(root, "rev-parse", "HEAD").decode().strip() != commit: + raise ValueError("source checkout does not match the declared commit") + epoch = int(git(root, "show", "-s", "--format=%ct", commit)) + if epoch != source["sourceDateEpoch"]: + raise ValueError("SOURCE_DATE_EPOCH does not match the source commit timestamp") + return git(root, "archive", commit), git(root, "rev-parse", f"{commit}^{{tree}}").decode().strip() + + +def compare_package(wheel_path: Path, source_files: dict[str, bytes]) -> list[dict[str, str]]: + """Compare every delivered package file, including JSON resources and py.typed.""" + with zipfile.ZipFile(wheel_path) as wheel: + names = wheel.namelist() + if len(names) != len(set(names)): + raise ValueError("wheel contains duplicate member paths") + delivered = {name: wheel.read(name) for name in names if name.startswith("limitless_library/")} + if delivered.keys() != source_files.keys(): + raise ValueError("wheel package file set does not match the source archive") + for name, expected in source_files.items(): + if delivered[name] != expected: + raise ValueError(f"wheel package file differs from source: {name}") + return [{"path": name, "sha256": hashlib.sha256(data).hexdigest()} for name, data in sorted(delivered.items())] + + +def reproduce(source_root: Path, recipe_path: Path, output: Path) -> dict[str, object]: + recipe = json.loads(recipe_path.read_text()) + if recipe["schemaVersion"] != "limitless.release-wheel-build/1": + raise ValueError("unsupported build recipe") + wheel_name = recipe["wheel"]["name"] + expected_digest = recipe["wheel"]["sha256"] + if Path(wheel_name).name != wheel_name or not wheel_name.endswith(".whl"): + raise ValueError("wheel name must be a plain wheel filename") + if not re.fullmatch(r"[0-9a-f]{64}", expected_digest): + raise ValueError("wheel SHA256 must be a full digest") + tools = recipe["tools"] + if f"{sys.version_info.major}.{sys.version_info.minor}" != tools["python"]: + raise ValueError("Python version does not match the build recipe") + for name, version in tools.items(): + if name != "python" and importlib.metadata.version(name) != version: + raise ValueError(f"build tool does not match the recipe: {name}=={version}") + archive, tree = source_archive(source_root, recipe["source"]) + with tarfile.open(fileobj=io.BytesIO(archive)) as source: + source_files = { + member.name.removeprefix("src/"): source.extractfile(member).read() + for member in source.getmembers() + if member.isfile() and member.name.startswith("src/limitless_library/") + } + if not source_files: + raise ValueError("source archive contains no core package files") + output.mkdir(parents=True, exist_ok=True) + results = [] + env = {**os.environ, "SOURCE_DATE_EPOCH": str(recipe["source"]["sourceDateEpoch"])} + with tempfile.TemporaryDirectory(prefix="release-rebuild-", dir=output) as temporary: + for copy in ("a", "b"): + root = Path(temporary) / copy + root.mkdir() + with tarfile.open(fileobj=io.BytesIO(archive)) as source: + source.extractall(root, filter="data") + dist = root / "wheel-output" + subprocess.run( + [sys.executable, "-m", "build", "--wheel", "--no-isolation", "--outdir", str(dist)], + cwd=root, + env=env, + check=True, + ) + wheels = list(dist.glob("*.whl")) + if len(wheels) != 1 or wheels[0].name != wheel_name: + raise ValueError("build did not produce the declared wheel") + observed_digest = hashlib.sha256(wheels[0].read_bytes()).hexdigest() + if observed_digest != expected_digest: + raise ValueError(f"rebuilt wheel digest differs from the shipped release: {observed_digest}") + package_files = compare_package(wheels[0], source_files) + results.append({"copy": copy, "sha256": observed_digest}) + if copy == "a": + shutil.copyfile(wheels[0], output / wheel_name) + run_id = os.environ.get("GITHUB_RUN_ID") + repository = os.environ.get("GITHUB_REPOSITORY") + report = { + "schemaVersion": "limitless.release-wheel-proof/1", + "source": {**recipe["source"], "tree": tree, "archiveSha256": hashlib.sha256(archive).hexdigest()}, + "wheel": recipe["wheel"], + "tools": {**tools, "pythonFullVersion": platform.python_version()}, + "builds": results, + "packageFiles": package_files, + "comparison": "Both clean source-archive builds match the complete shipped wheel SHA256; all package files match source.", + "builder": { + "repository": repository, + "commit": os.environ.get("GITHUB_SHA"), + "runUrl": f"https://github.com/{repository}/actions/runs/{run_id}" if run_id and repository else None, + "runAttempt": os.environ.get("GITHUB_RUN_ATTEMPT"), + }, + } + (output / "wheel-build.json").write_text(json.dumps(report, indent=2) + "\n") + return report + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-root", type=Path, required=True) + parser.add_argument("--recipe", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + arguments = parser.parse_args() + try: + report = reproduce(arguments.source_root.resolve(), arguments.recipe, arguments.output.resolve()) + except (OSError, ValueError, KeyError, subprocess.CalledProcessError, zipfile.BadZipFile) as error: + print(f"release wheel reproduction failed: {error}", file=sys.stderr) + return 1 + print(json.dumps({"source": report["source"], "wheel": report["wheel"], "builds": report["builds"]}, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_release_wheel_provenance.py b/tests/test_release_wheel_provenance.py new file mode 100644 index 0000000..97255c7 --- /dev/null +++ b/tests/test_release_wheel_provenance.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +import importlib.util +import subprocess +import zipfile +from pathlib import Path + +import pytest + +SCRIPT = Path(__file__).parents[1] / "scripts" / "reproduce-release-wheel.py" +spec = importlib.util.spec_from_file_location("release_wheel_provenance", SCRIPT) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + + +def write_wheel(path: Path, files: dict[str, bytes]) -> None: + with zipfile.ZipFile(path, "w") as wheel: + for name, content in files.items(): + wheel.writestr(name, content) + + +def test_compares_python_and_package_resources(tmp_path: Path) -> None: + files = {"limitless_library/cli.py": b"pass\n", "limitless_library/schemas/capsule.json": b"{}"} + wheel = tmp_path / "core.whl" + write_wheel(wheel, {**files, "core.dist-info/METADATA": b"Name: core\n"}) + result = module.compare_package(wheel, files) + assert [row["path"] for row in result] == sorted(files) + + +@pytest.mark.parametrize("change", ["modified-python", "modified-resource", "added", "missing"]) +def test_rejects_changed_or_incomplete_package(tmp_path: Path, change: str) -> None: + source = {"limitless_library/cli.py": b"pass\n", "limitless_library/config.json": b"{}"} + delivered = dict(source) + if change == "modified-python": + delivered["limitless_library/cli.py"] = b"raise RuntimeError\n" + elif change == "modified-resource": + delivered["limitless_library/config.json"] = b'{"changed": true}' + elif change == "added": + delivered["limitless_library/extra.py"] = b"pass\n" + else: + del delivered["limitless_library/config.json"] + wheel = tmp_path / "core.whl" + write_wheel(wheel, delivered) + with pytest.raises(ValueError, match="wheel package file"): + module.compare_package(wheel, source) + + +def test_rejects_duplicate_wheel_members(tmp_path: Path) -> None: + wheel = tmp_path / "core.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("limitless_library/cli.py", b"pass\n") + with pytest.warns(UserWarning, match="Duplicate name"): + archive.writestr("limitless_library/cli.py", b"pass\n") + with pytest.raises(ValueError, match="duplicate"): + module.compare_package(wheel, {"limitless_library/cli.py": b"pass\n"}) + + +def test_requires_the_exact_source_commit(tmp_path: Path) -> None: + subprocess.run(["git", "init", "-q", str(tmp_path)], check=True) + subprocess.run( + [ + "git", + "-C", + str(tmp_path), + "-c", + "user.name=Test", + "-c", + "user.email=test@example.invalid", + "commit", + "-q", + "--allow-empty", + "-m", + "fixture", + ], + check=True, + ) + with pytest.raises(ValueError, match="checkout does not match"): + module.source_archive(tmp_path, {"commit": "0" * 40, "sourceDateEpoch": 0}) From 9c9a7bdb2f4a730e74d586ebf5e22ce77aecc14d Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Mon, 12 Oct 2026 22:40:09 -0400 Subject: [PATCH 2/3] Pin the exact Python patch version for wheel reproduction --- .github/workflows/wheel-provenance.yml | 2 +- docs/WHEEL-PROVENANCE.md | 2 +- docs/releases/0.1.0a1-wheel.json | 2 +- scripts/reproduce-release-wheel.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheel-provenance.yml b/.github/workflows/wheel-provenance.yml index 39c2510..5f0f0ec 100644 --- a/.github/workflows/wheel-provenance.yml +++ b/.github/workflows/wheel-provenance.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.12" + python-version: "3.12.3" - name: Install exact build tools run: python -m pip install pip==26.2.1 build==1.5.0 setuptools==83.0.0 wheel==0.48.0 - name: Rebuild twice and compare all delivered package files diff --git a/docs/WHEEL-PROVENANCE.md b/docs/WHEEL-PROVENANCE.md index c8ad87c..c1433e6 100644 --- a/docs/WHEEL-PROVENANCE.md +++ b/docs/WHEEL-PROVENANCE.md @@ -49,7 +49,7 @@ can also be verified offline with GitHub CLI's `--bundle` option. ## Reproduce locally -Use Python 3.12 with `pip==26.2.1`, `build==1.5.0`, `setuptools==83.0.0`, and +Use Python 3.12.3 with `pip==26.2.1`, `build==1.5.0`, `setuptools==83.0.0`, and `wheel==0.48.0`. Keep the build tooling checkout separate from a detached checkout of the historical source commit, then run: diff --git a/docs/releases/0.1.0a1-wheel.json b/docs/releases/0.1.0a1-wheel.json index 056bc22..f67b99c 100644 --- a/docs/releases/0.1.0a1-wheel.json +++ b/docs/releases/0.1.0a1-wheel.json @@ -10,7 +10,7 @@ "sha256": "1f776299c684e1b52430585b640917a37ca88eac9ef0db0414ad0e18e8094986" }, "tools": { - "python": "3.12", + "python": "3.12.3", "pip": "26.2.1", "build": "1.5.0", "setuptools": "83.0.0", diff --git a/scripts/reproduce-release-wheel.py b/scripts/reproduce-release-wheel.py index 3cf9368..c806250 100755 --- a/scripts/reproduce-release-wheel.py +++ b/scripts/reproduce-release-wheel.py @@ -62,7 +62,7 @@ def reproduce(source_root: Path, recipe_path: Path, output: Path) -> dict[str, o if not re.fullmatch(r"[0-9a-f]{64}", expected_digest): raise ValueError("wheel SHA256 must be a full digest") tools = recipe["tools"] - if f"{sys.version_info.major}.{sys.version_info.minor}" != tools["python"]: + if platform.python_version() != tools["python"]: raise ValueError("Python version does not match the build recipe") for name, version in tools.items(): if name != "python" and importlib.metadata.version(name) != version: From 8a2f03c0c0050cc582f3f0c1f612c7d134297bff Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Mon, 12 Oct 2026 22:42:24 -0400 Subject: [PATCH 3/3] Make wheel permissions and build dependencies reproducible --- .github/workflows/wheel-provenance.yml | 2 +- docs/WHEEL-PROVENANCE.md | 9 ++++++--- docs/releases/0.1.0a1-wheel.json | 5 ++++- scripts/reproduce-release-wheel.py | 5 +++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheel-provenance.yml b/.github/workflows/wheel-provenance.yml index 5f0f0ec..8804cf1 100644 --- a/.github/workflows/wheel-provenance.yml +++ b/.github/workflows/wheel-provenance.yml @@ -41,7 +41,7 @@ jobs: with: python-version: "3.12.3" - name: Install exact build tools - run: python -m pip install pip==26.2.1 build==1.5.0 setuptools==83.0.0 wheel==0.48.0 + run: python -m pip install pip==26.2.1 build==1.5.0 setuptools==83.0.0 wheel==0.48.0 packaging==26.3 pyproject_hooks==1.3.3 - name: Rebuild twice and compare all delivered package files run: | python tooling/scripts/reproduce-release-wheel.py \ diff --git a/docs/WHEEL-PROVENANCE.md b/docs/WHEEL-PROVENANCE.md index c1433e6..23898ef 100644 --- a/docs/WHEEL-PROVENANCE.md +++ b/docs/WHEEL-PROVENANCE.md @@ -7,7 +7,7 @@ Its package sources are public at commit [`b9249e8de3e2cf558b70719dcd6e7a2ec44dd352`](https://github.com/Univeracity/limitlesslibrary/tree/b9249e8de3e2cf558b70719dcd6e7a2ec44dd352/src/limitless_library). [The build recipe](releases/0.1.0a1-wheel.json) records the source commit, -its timestamp, the exact wheel digest, and the pinned build tools. +its timestamp, the exact wheel digest, the build umask, and the pinned build tools. The [source evidence workflow](../.github/workflows/wheel-provenance.yml): 1. Checks out that exact public source commit and verifies its identity. @@ -49,8 +49,11 @@ can also be verified offline with GitHub CLI's `--bundle` option. ## Reproduce locally -Use Python 3.12.3 with `pip==26.2.1`, `build==1.5.0`, `setuptools==83.0.0`, and -`wheel==0.48.0`. Keep the build tooling checkout separate from a detached checkout +Use Python 3.12.3 on Linux with `pip==26.2.1`, `build==1.5.0`, +`setuptools==83.0.0`, `wheel==0.48.0`, `packaging==26.3`, and +`pyproject_hooks==1.3.3`. The helper sets the recipe's `0002` umask for the build +subprocess; wheel file permissions are part of the complete artifact digest. +Keep the build tooling checkout separate from a detached checkout of the historical source commit, then run: ```sh diff --git a/docs/releases/0.1.0a1-wheel.json b/docs/releases/0.1.0a1-wheel.json index f67b99c..a864c6f 100644 --- a/docs/releases/0.1.0a1-wheel.json +++ b/docs/releases/0.1.0a1-wheel.json @@ -9,11 +9,14 @@ "name": "limitless_library-0.1.0a1-py3-none-any.whl", "sha256": "1f776299c684e1b52430585b640917a37ca88eac9ef0db0414ad0e18e8094986" }, + "buildUmask": "0002", "tools": { "python": "3.12.3", "pip": "26.2.1", "build": "1.5.0", "setuptools": "83.0.0", - "wheel": "0.48.0" + "wheel": "0.48.0", + "packaging": "26.3", + "pyproject_hooks": "1.3.3" } } diff --git a/scripts/reproduce-release-wheel.py b/scripts/reproduce-release-wheel.py index c806250..bd3fe18 100755 --- a/scripts/reproduce-release-wheel.py +++ b/scripts/reproduce-release-wheel.py @@ -61,6 +61,9 @@ def reproduce(source_root: Path, recipe_path: Path, output: Path) -> dict[str, o raise ValueError("wheel name must be a plain wheel filename") if not re.fullmatch(r"[0-9a-f]{64}", expected_digest): raise ValueError("wheel SHA256 must be a full digest") + build_umask = recipe["buildUmask"] + if not isinstance(build_umask, str) or not re.fullmatch(r"0[0-7]{3}", build_umask): + raise ValueError("build umask must be an octal permission mask") tools = recipe["tools"] if platform.python_version() != tools["python"]: raise ValueError("Python version does not match the build recipe") @@ -90,6 +93,7 @@ def reproduce(source_root: Path, recipe_path: Path, output: Path) -> dict[str, o [sys.executable, "-m", "build", "--wheel", "--no-isolation", "--outdir", str(dist)], cwd=root, env=env, + umask=int(build_umask, 8), check=True, ) wheels = list(dist.glob("*.whl")) @@ -108,6 +112,7 @@ def reproduce(source_root: Path, recipe_path: Path, output: Path) -> dict[str, o "schemaVersion": "limitless.release-wheel-proof/1", "source": {**recipe["source"], "tree": tree, "archiveSha256": hashlib.sha256(archive).hexdigest()}, "wheel": recipe["wheel"], + "buildUmask": build_umask, "tools": {**tools, "pythonFullVersion": platform.python_version()}, "builds": results, "packageFiles": package_files,