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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,13 @@ jobs:
cache: pip
- run: >-
python -m pip install
"build==1.2.2.post1" "PyYAML==6.0.3" "setuptools==75.8.0" "wheel==0.45.1"
"build==1.2.2.post1" "pipx==1.8.0" "PyYAML==6.0.3"
"setuptools==75.8.0" "wheel==0.45.1"
- run: >-
python -m pip download
--only-binary=:all:
--dest wheelhouse
"pip==25.3"
"PyYAML==6.0.3"
- run: python scripts/build_release.py --out dist
- run: >-
Expand Down
27 changes: 24 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
cache: pip
- run: >-
python -m pip install
"build==1.2.2.post1" "PyYAML==6.0.3" "setuptools==75.8.0" "wheel==0.45.1"
"build==1.2.2.post1" "pipx==1.8.0" "PyYAML==6.0.3"
"setuptools==75.8.0" "wheel==0.45.1"
- run: python scripts/build_release.py --out dist
- run: python -m venv /tmp/sourcebound-release
- run: /tmp/sourcebound-release/bin/pip install dist/*.whl
Expand All @@ -33,6 +34,7 @@ jobs:
python -m pip download
--only-binary=:all:
--dest /tmp/sourcebound-wheelhouse
"pip==25.3"
"PyYAML==6.0.3"
- run: >-
python scripts/test_readme_quickstart.py
Expand Down Expand Up @@ -80,7 +82,6 @@ jobs:
fi
python scripts/publish_release.py "${args[@]}"
- name: Stage the PyPI distribution
if: vars.PYPI_PUBLISH_ENABLED == 'true'
shell: bash
run: |
mapfile -t wheels < <(find dist -maxdepth 1 -type f -name '*.whl' -print)
Expand All @@ -91,15 +92,35 @@ jobs:
mkdir -p pypi-dist
cp -f -- "${wheels[0]}" pypi-dist/
- name: Publish the attested wheel to PyPI
if: vars.PYPI_PUBLISH_ENABLED == 'true'
id: publish-pypi
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
with:
packages-dir: pypi-dist/
skip-existing: true
- name: Install publication verifiers
if: ${{ always() && steps.publish-pypi.outcome != 'skipped' }}
run: python -m pip install "pipx==1.8.0" "uv==0.8.22"
- name: Verify published bytes and installers
if: ${{ always() && steps.publish-pypi.outcome != 'skipped' }}
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
version="${GITHUB_REF_NAME#v}"
python scripts/verify_published_release.py \
--repo "$GITHUB_REPOSITORY" \
--tag "$GITHUB_REF_NAME" \
--version "$version" \
--dist dist \
--root . \
--out sourcebound-publication-verification.json
- name: Upload publication receipt
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sourcebound-release-publication-${{ github.sha }}
path: |
release-publication.json
sourcebound-publication-verification.json
sourcebound-quickstart.json
if-no-files-found: error
1 change: 1 addition & 0 deletions .sourcebound.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ projections:
- docs/EXTENSIONS.md
- docs/ARCHITECTURE.md
- docs/INSTALL.md
- docs/VERIFY_RELEASE.md
- docs/IMPROVEMENTS.md
- docs/LIFECYCLE_EVIDENCE.md
- docs/REVIEW_LEDGER.md
Expand Down
18 changes: 9 additions & 9 deletions .sourcebound/context/contributor.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Context bundle: contributor

- Source ref: `WORKTREE`
- Corpus sha256: `2bbea252669405281abe5b3e84a61eeec205257d99b18c78bf52996322cb38e0`
- Corpus sha256: `90daf326a4950cf2e97a04e8f3a0490ee702637513adc392d335c8db71422288`
- Content: exact canonical document bytes

## Canonical document: README.md

- Source: [README.md](../../README.md)
- Content sha256: `d943d7a09a22e2a67c94b370b9a92bc000de02d0e460cb6bcf6cb31022a3534c`
- Content sha256: `7bae61d93eef29e6ac61bf489e61f3ca2a7857295ee0cf0176ba513d2770dce4`

<!-- sourcebound:canonical README.md begin -->
# Sourcebound
Expand Down Expand Up @@ -49,18 +49,18 @@ Human review can improve a sentence. It cannot make the sentence fail when its d

## Install in the repository you want to protect

From that repository, download the latest stable wheel, install it in an isolated environment, and run the manifest-free audit:
Install the stable CLI in an isolated environment, then run the manifest-free audit from the
repository you want to protect:

```bash
release_dir="$(mktemp -d)"
gh release download --repo owieschon/sourcebound \
--pattern 'sourcebound-*-py3-none-any.whl' --dir "$release_dir"
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "$release_dir"/sourcebound-*.whl
pipx install sourcebound
sourcebound audit
```

Use `uv tool install sourcebound` instead when `uv` owns your command-line tools. The
[installation guide](docs/INSTALL.md) covers offline wheelhouses, upgrades, and rollback. The
[release verification guide](docs/VERIFY_RELEASE.md) checks published bytes and provenance.

After reviewing the assessment, inspect the files that `init` proposes before accepting its gate:

```bash
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ Human review can improve a sentence. It cannot make the sentence fail when its d

## Install in the repository you want to protect

From that repository, download the latest stable wheel, install it in an isolated environment, and run the manifest-free audit:
Install the stable CLI in an isolated environment, then run the manifest-free audit from the
repository you want to protect:

```bash
release_dir="$(mktemp -d)"
gh release download --repo owieschon/sourcebound \
--pattern 'sourcebound-*-py3-none-any.whl' --dir "$release_dir"
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "$release_dir"/sourcebound-*.whl
pipx install sourcebound
sourcebound audit
```

Use `uv tool install sourcebound` instead when `uv` owns your command-line tools. The
[installation guide](docs/INSTALL.md) covers offline wheelhouses, upgrades, and rollback. The
[release verification guide](docs/VERIFY_RELEASE.md) checks published bytes and provenance.

After reviewing the assessment, inspect the files that `init` proposes before accepting its gate:

```bash
Expand Down
80 changes: 23 additions & 57 deletions docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ or move between versions without changing repository documentation. Each path en
version or artifact check, so the executable is known before it becomes a gate.
<!-- sourcebound:end purpose -->

**[Install the latest stable release](#install-the-latest-stable-release)**.
**[Install Sourcebound with pipx](#install-with-a-python-tool-installer)**.

## Install with a Python tool installer

After a stable Sourcebound release reaches PyPI, install the CLI in an isolated environment:
Install the stable Sourcebound CLI from PyPI in an isolated environment:

```bash
uv tool install sourcebound
pipx install sourcebound
sourcebound --version
```

Use `pipx install sourcebound` for the same persistent command, or `uvx sourcebound --help` once.
PyPI receives the same attested wheel published as a GitHub Release asset.
Use `uv tool install sourcebound` for the same persistent command, or `uvx sourcebound --help` for
one invocation. The release workflow publishes the same attested wheel to PyPI and GitHub.

## Install the latest stable release
## Install from a GitHub release

From the repository you want to protect, download the latest stable wheel and let `pip` resolve
PyYAML from your configured package index:
Expand Down Expand Up @@ -76,75 +76,41 @@ making Node.js a requirement for every Sourcebound installation.

## Upgrade, roll back, or remove the executable

Install the newer wheel, then preview any requested manifest change before writing:
Use the same tool that installed Sourcebound. Upgrade the executable, confirm its version, then
preview any requested manifest change before writing:

```bash
python -m pip install --upgrade ./sourcebound-*.whl
pipx upgrade sourcebound
sourcebound migrate
sourcebound migrate --write
```

`migrate --write` stores the prior manifest bytes in `.sourcebound.yml.v0.bak`. Restore them with
`sourcebound migrate --rollback`. Reinstall the prior wheel to roll back the executable.
With `uv`, replace the first command with `uv tool upgrade sourcebound`.

Remove the package with:
`migrate --write` stores the prior manifest bytes in `.sourcebound.yml.v0.bak`. Restore them with
`sourcebound migrate --rollback`. Reinstall an exact prior version to roll back the executable:

```bash
python -m pip uninstall sourcebound
pipx install --force "sourcebound==<version>"
```

Uninstalling leaves repository manifests and documentation in place.

## Verify release artifacts

Download the wheel and its checksum file into one directory:
With `uv`, run `uv tool install --force "sourcebound==<version>"` instead. If you installed a local
wheel in a virtual environment, replace it with:

```bash
artifact_dir="$(mktemp -d)"
gh release download --repo owieschon/sourcebound \
--pattern 'sourcebound-*-py3-none-any.whl' \
--pattern SHA256SUMS \
--dir "$artifact_dir"
cd "$artifact_dir"
python -m pip install --upgrade ./sourcebound-*.whl
```

### Check the wheel bytes

Verify the one wheel without requiring every release asset to be present:
Remove the tool with the installer that owns it:

```bash
python3 - <<'PY'
from hashlib import sha256
from pathlib import Path

wheels = list(Path(".").glob("sourcebound-*.whl"))
if len(wheels) != 1:
raise SystemExit(f"expected one wheel, found {len(wheels)}")
expected = {
filename: digest
for digest, filename in (
line.split(maxsplit=1) for line in Path("SHA256SUMS").read_text().splitlines()
)
}
actual = sha256(wheels[0].read_bytes()).hexdigest()
if expected.get(wheels[0].name) != actual:
raise SystemExit("wheel checksum mismatch")
print(f"{wheels[0].name}: {actual}")
PY
pipx uninstall sourcebound
```

### Verify the attestation
With `uv`, run `uv tool uninstall sourcebound`.

Ask GitHub to match the wheel to its build provenance:

```bash
gh attestation verify ./sourcebound-*.whl \
--repo owieschon/sourcebound
```

The checksum step is local. The attestation command needs GitHub access, so run it outside a
network-blocked environment. The release gate also exercises upgrade, executable rollback, a second
upgrade, and uninstall.
Uninstalling leaves repository manifests and documentation in place.

Return to the [support guide](SUPPORT.md) to adopt an existing corpus, pin the reusable CI gate, or
build a diagnostic bundle.
Use the [release verification guide](VERIFY_RELEASE.md) when you need to check published wheel bytes
or provenance. Return to the [support guide](SUPPORT.md) to adopt an existing corpus, pin the
reusable CI gate, or build a diagnostic bundle.
8 changes: 4 additions & 4 deletions docs/SURFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ The catalog binding catches additions, removals, and replacements across the det
<!-- sourcebound:begin repository-surface -->
| surface | discovered | examples |
| --- | ---: | --- |
| api-symbol | 305 | `AcceptanceCase`, `Assertion`, `AuditFinding`, and 302 more |
| api-symbol | 307 | `AcceptanceCase`, `Assertion`, `AuditFinding`, and 304 more |
| cli-command | 43 | `audit`, `benchmark`, `binding`, and 40 more |
| cli-option | 89 | `--accept-hygiene-baseline`, `--base`, `--binding`, and 86 more |
| cli-option | 97 | `--accept-hygiene-baseline`, `--attempts`, `--base`, and 94 more |
| package | 2 | `sourcebound`, `sourcebound-mdx-parser-build` |
| package-script | 1 | `build` |
| runtime-constraint | 3 | `ES modules`, `Python >=3.10`, `node >=20` |
| test-suite | 69 | `scripts/test_readme_quickstart.py`, `scripts/test_release_lifecycle.py`, `tests/test_accessibility.py`, and 66 more |
| test-suite | 70 | `scripts/test_readme_quickstart.py`, `scripts/test_release_lifecycle.py`, `tests/test_accessibility.py`, and 67 more |

<!-- sourcebound:inventory-sha256 c160ab368fec974816e9375e3b2a86d8d656c55f0dbab12aff6d90e56f83bfe5 -->
<!-- sourcebound:inventory-sha256 8ddfe1e73134157c5ae710d9a3926ca74b948db3fe68926d4a63efe90c4206bf -->
<!-- sourcebound:end repository-surface -->
70 changes: 70 additions & 0 deletions docs/VERIFY_RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Verify a Sourcebound release

<!-- sourcebound:policy register-v2 -->
<!-- sourcebound:purpose -->
Operators use this guide to confirm that a published Sourcebound wheel matches its checksum and
GitHub attestation before they trust it as a local or CI gate.
<!-- sourcebound:end purpose -->

**[Download the published artifacts](#download-the-published-artifacts)**.

## Download the published artifacts

Download the wheel and its checksum file into one directory:

```bash
artifact_dir="$(mktemp -d)"
gh release download --repo owieschon/sourcebound \
--pattern 'sourcebound-*-py3-none-any.whl' \
--pattern SHA256SUMS \
--dir "$artifact_dir"
cd "$artifact_dir"
```

## Check the wheel bytes

Verify the one wheel without requiring every release asset to be present:

```bash
python3 - <<'PY'
from hashlib import sha256
from pathlib import Path

wheels = list(Path(".").glob("sourcebound-*.whl"))
if len(wheels) != 1:
raise SystemExit(f"expected one wheel, found {len(wheels)}")
expected = {
filename: digest
for digest, filename in (
line.split(maxsplit=1) for line in Path("SHA256SUMS").read_text().splitlines()
)
}
actual = sha256(wheels[0].read_bytes()).hexdigest()
if expected.get(wheels[0].name) != actual:
raise SystemExit("wheel checksum mismatch")
print(f"{wheels[0].name}: {actual}")
PY
```

The printed digest must match the wheel entry in `SHA256SUMS`.

## Verify the attestation

Ask GitHub to match the wheel to its build provenance:

```bash
gh attestation verify ./sourcebound-*.whl \
--repo owieschon/sourcebound
```

The checksum step is local. The attestation command needs GitHub access, so run it outside a
network-blocked environment. Each release workflow also compares the GitHub and PyPI wheel bytes,
then installs that exact version with both `pipx` and `uv`.

When publication reaches PyPI, the workflow uploads a
`sourcebound.publication-verification.v1` receipt with `ok: true` or the observed failure. The
receipt records the local checksum, wheel digests observed from GitHub and PyPI, attestation status,
and both installed versions on success.

Return to the [installation guide](INSTALL.md) to install, upgrade, roll back, or remove the
executable.
2 changes: 1 addition & 1 deletion docs/learn/tutorial-catch-a-lying-doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
This tutorial is for maintainers who want to see one source-bound fact fail and recover in a disposable repository. It turns an easy-to-miss command rename into a named check failure, a region-only repair, and a verified projection using an installed sourcebound release.
<!-- sourcebound:end purpose -->

**[Install the stable release](../INSTALL.md#install-the-latest-stable-release)**, then
**[Install the stable release](../INSTALL.md#install-from-a-github-release)**, then
build the disposable repository below.

Success appears in the [outcome receipt](../SUPPORT.md#record-local-outcomes) after the last
Expand Down
Loading