Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions .github/workflows/wheel-provenance.yml
Original file line number Diff line number Diff line change
@@ -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.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 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 \
--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
3 changes: 2 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
64 changes: 64 additions & 0 deletions docs/WHEEL-PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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, 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.
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.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
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
```
22 changes: 22 additions & 0 deletions docs/releases/0.1.0a1-wheel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"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"
},
"buildUmask": "0002",
"tools": {
"python": "3.12.3",
"pip": "26.2.1",
"build": "1.5.0",
"setuptools": "83.0.0",
"wheel": "0.48.0",
"packaging": "26.3",
"pyproject_hooks": "1.3.3"
}
}
147 changes: 147 additions & 0 deletions scripts/reproduce-release-wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/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")
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")
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,
umask=int(build_umask, 8),
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"],
"buildUmask": build_umask,
"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())
Loading
Loading