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
136 changes: 130 additions & 6 deletions .github/workflows/python-publish-testpypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ jobs:
uses: ./.github/workflows/darwin-x86_64-validation.yml

build-distributions:
needs: [verify-ci, linux-cli-validation, darwin-validation]
needs:
- verify-ci
- linux-cli-validation
- darwin-validation
runs-on: ubuntu-latest

steps:
Expand All @@ -59,27 +62,148 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install --upgrade pip uv
- name: Cache uv
uses: actions/cache@v5
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-testpypi-build-${{ hashFiles('uv.lock') }}
restore-keys: |
uv-${{ runner.os }}-testpypi-build-
- name: Build distributions
run: |
python -m pip install --upgrade pip build twine
python -m build
run: uv build
- name: Check distribution metadata
run: python -m twine check --strict dist/*
run: uvx --from twine twine check --strict dist/*
- name: Smoke-test wheel installation
run: ./scripts/workflows/smoke_test_wheel_install.sh 'dist/*.whl'
- name: Record distribution hashes
run: |
cd dist
sha256sum -- * > SHA256SUMS
- name: Upload distributions artifact
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: dist/
if-no-files-found: error
retention-days: 14

release-required:
runs-on: ubuntu-latest
needs:
- verify-ci
- linux-cli-validation
- darwin-validation
- build-distributions
if: ${{ always() && github.event.release.prerelease == true }}

steps:
- name: Evaluate required TestPyPI gates
env:
VERIFY_CI_RESULT: ${{ needs.verify-ci.result }}
LINUX_CLI_RESULT: ${{ needs.linux-cli-validation.result }}
DARWIN_RESULT: ${{ needs.darwin-validation.result }}
BUILD_RESULT: ${{ needs.build-distributions.result }}
run: |
set -euo pipefail

{
echo "### TestPyPI release-required gates"
echo
echo "| Gate | Result |"
echo "| --- | --- |"
echo "| verify-ci | ${VERIFY_CI_RESULT} |"
echo "| linux-cli-validation | ${LINUX_CLI_RESULT} |"
echo "| darwin-validation | ${DARWIN_RESULT} |"
echo "| build-distributions | ${BUILD_RESULT} |"
} >> "$GITHUB_STEP_SUMMARY"

for gate in VERIFY_CI_RESULT LINUX_CLI_RESULT DARWIN_RESULT BUILD_RESULT; do
if [[ "${!gate}" != "success" ]]; then
echo "Required TestPyPI gate failed or skipped: ${gate}=${!gate}" >&2
exit 1
fi
done

release-evidence:
runs-on: ubuntu-latest
needs:
- verify-ci
- release-required
- build-distributions
if: ${{ needs.release-required.result == 'success' }}

steps:
- name: Download distributions artifact
uses: actions/download-artifact@v8
with:
name: python-package-distributions
path: dist/
- name: Write TestPyPI release evidence bundle
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_ID: ${{ github.event.release.id }}
RUN_ID: ${{ github.run_id }}
HEAD_SHA: ${{ needs.verify-ci.outputs.head_sha }}
REQUIRED_RESULT: ${{ needs.release-required.result }}
BUILD_RESULT: ${{ needs.build-distributions.result }}
run: |
set -euo pipefail
mkdir -p release-evidence
python - <<'PY'
import json
import os
from pathlib import Path

hashes_path = Path("dist/SHA256SUMS")
payload = {
"target": "testpypi",
"release_tag": os.environ["RELEASE_TAG"],
"release_id": os.environ["RELEASE_ID"],
"run_id": os.environ["RUN_ID"],
"head_sha": os.environ["HEAD_SHA"],
"required_result": os.environ["REQUIRED_RESULT"],
"build_result": os.environ["BUILD_RESULT"],
"dist_hashes": hashes_path.read_text(encoding="utf-8").splitlines(),
}
Path("release-evidence/release-evidence.json").write_text(
json.dumps(payload, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
PY
- name: Upload TestPyPI release evidence artifact
uses: actions/upload-artifact@v7
with:
name: testpypi-release-evidence
path: release-evidence/
if-no-files-found: error
retention-days: 30
- name: Write TestPyPI evidence summary
run: |
{
echo "### TestPyPI release evidence"
echo
echo "- Evidence artifact: \`testpypi-release-evidence\`"
echo "- Distribution artifact: \`python-package-distributions\`"
echo "- Head SHA: \`${{ needs.verify-ci.outputs.head_sha }}\`"
echo
echo "TestPyPI publishing is guarded by \`SER_ALLOW_TESTPYPI_PUBLISH=true\` until the environment and trusted publisher are verified."
} >> "$GITHUB_STEP_SUMMARY"

publish-to-testpypi:
needs: [verify-ci, linux-cli-validation, darwin-validation, build-distributions]
needs:
- release-required
- release-evidence
if: ${{ github.event.release.prerelease == true && vars.SER_ALLOW_TESTPYPI_PUBLISH == 'true' }}
runs-on: ubuntu-latest

environment:
name: testpypi
url: https://test.pypi.org/p/ser
permissions:
actions: read
contents: read
id-token: write

steps:
Expand Down
161 changes: 142 additions & 19 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,27 @@ jobs:
with:
run_accurate_research: false

selfhosted-gpu-validation:
release-advisory-selfhosted-gpu-validation:
needs: verify-ci
uses: ./.github/workflows/linux-selfhosted-gpu-validation.yml
with:
run_cuda: true
run_xpu: false
run_accurate_research: false

full-dataset-quality-gate:
release-advisory-full-dataset-quality-gate:
needs: verify-ci
uses: ./.github/workflows/full-dataset-quality-gate-regression.yml
with:
run_training: true
require_pass: false

build-distributions:
needs:
[
verify-ci,
linux-cli-validation,
darwin-validation,
macos-mps-validation,
]
- verify-ci
- linux-cli-validation
- darwin-validation
- macos-mps-validation
runs-on: ubuntu-latest

steps:
Expand All @@ -85,34 +84,158 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install --upgrade pip uv
- name: Cache uv
uses: actions/cache@v5
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-release-build-${{ hashFiles('uv.lock') }}
restore-keys: |
uv-${{ runner.os }}-release-build-
- name: Build distributions
run: |
python -m pip install --upgrade pip build twine
python -m build
run: uv build
- name: Check distribution metadata
run: python -m twine check --strict dist/*
run: uvx --from twine twine check --strict dist/*
- name: Smoke-test wheel installation
run: ./scripts/workflows/smoke_test_wheel_install.sh 'dist/*.whl'
- name: Record distribution hashes
run: |
cd dist
sha256sum -- * > SHA256SUMS
- name: Upload distributions artifact
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: dist/
if-no-files-found: error
retention-days: 14

release-required:
runs-on: ubuntu-latest
needs:
- verify-ci
- linux-cli-validation
- darwin-validation
- macos-mps-validation
- build-distributions
if: ${{ always() && github.event.release.prerelease == false }}

steps:
- name: Evaluate required release gates
env:
VERIFY_CI_RESULT: ${{ needs.verify-ci.result }}
LINUX_CLI_RESULT: ${{ needs.linux-cli-validation.result }}
DARWIN_RESULT: ${{ needs.darwin-validation.result }}
MACOS_MPS_RESULT: ${{ needs.macos-mps-validation.result }}
BUILD_RESULT: ${{ needs.build-distributions.result }}
run: |
set -euo pipefail

{
echo "### Release-required gates"
echo
echo "| Gate | Result |"
echo "| --- | --- |"
echo "| verify-ci | ${VERIFY_CI_RESULT} |"
echo "| linux-cli-validation | ${LINUX_CLI_RESULT} |"
echo "| darwin-validation | ${DARWIN_RESULT} |"
echo "| macos-mps-validation | ${MACOS_MPS_RESULT} |"
echo "| build-distributions | ${BUILD_RESULT} |"
echo
echo "Self-hosted GPU and full-dataset gates are advisory until runner/quality ownership is verified."
} >> "$GITHUB_STEP_SUMMARY"

for gate in \
VERIFY_CI_RESULT \
LINUX_CLI_RESULT \
DARWIN_RESULT \
MACOS_MPS_RESULT \
BUILD_RESULT; do
if [[ "${!gate}" != "success" ]]; then
echo "Required release gate failed or skipped: ${gate}=${!gate}" >&2
exit 1
fi
done

release-evidence:
runs-on: ubuntu-latest
needs:
- verify-ci
- release-required
- build-distributions
if: ${{ needs.release-required.result == 'success' }}

steps:
- name: Download distributions artifact
uses: actions/download-artifact@v8
with:
name: python-package-distributions
path: dist/
- name: Write release evidence bundle
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_ID: ${{ github.event.release.id }}
RUN_ID: ${{ github.run_id }}
HEAD_SHA: ${{ needs.verify-ci.outputs.head_sha }}
REQUIRED_RESULT: ${{ needs.release-required.result }}
BUILD_RESULT: ${{ needs.build-distributions.result }}
run: |
set -euo pipefail
mkdir -p release-evidence
python - <<'PY'
import json
import os
from pathlib import Path

hashes_path = Path("dist/SHA256SUMS")
payload = {
"target": "pypi",
"release_tag": os.environ["RELEASE_TAG"],
"release_id": os.environ["RELEASE_ID"],
"run_id": os.environ["RUN_ID"],
"head_sha": os.environ["HEAD_SHA"],
"required_result": os.environ["REQUIRED_RESULT"],
"build_result": os.environ["BUILD_RESULT"],
"dist_hashes": hashes_path.read_text(encoding="utf-8").splitlines(),
}
Path("release-evidence/release-evidence.json").write_text(
json.dumps(payload, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
PY
- name: Upload release evidence artifact
uses: actions/upload-artifact@v7
with:
name: pypi-release-evidence
path: release-evidence/
if-no-files-found: error
retention-days: 30
- name: Write release evidence summary
run: |
{
echo "### PyPI release evidence"
echo
echo "- Evidence artifact: \`pypi-release-evidence\`"
echo "- Distribution artifact: \`python-package-distributions\`"
echo "- Head SHA: \`${{ needs.verify-ci.outputs.head_sha }}\`"
echo
echo "Production publishing is additionally guarded by \`SER_ALLOW_PYPI_PUBLISH=true\` until PyPI ownership/trusted-publisher state is verified."
} >> "$GITHUB_STEP_SUMMARY"

publish-to-pypi:
needs:
[
verify-ci,
linux-cli-validation,
darwin-validation,
macos-mps-validation,
build-distributions,
]
- release-required
- release-evidence
if: ${{ github.event.release.prerelease == false && vars.SER_ALLOW_PYPI_PUBLISH == 'true' }}
runs-on: ubuntu-latest

environment:
name: pypi
url: https://pypi.org/p/ser
permissions:
actions: read
contents: read
id-token: write

steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ ser/models/*.json
.coverage
.coverage.*
coverage.xml
htmlcov/
reports/

docs/ci/ci-cd-quality-implementation-journal.md
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,7 @@ uv run pytest -q \
- Contributor guide: [CONTRIBUTING.md](https://github.com/jsugg/ser/blob/main/CONTRIBUTING.md)
- Compatibility details: [docs/compatibility-matrix.md](https://github.com/jsugg/ser/blob/main/docs/compatibility-matrix.md)
- Hardware validation workflows: [docs/ci/hardware-validation.md](https://github.com/jsugg/ser/blob/main/docs/ci/hardware-validation.md)
- Release and rollback runbook: [docs/release-and-rollback.md](https://github.com/jsugg/ser/blob/main/docs/release-and-rollback.md)
- Security policy: [SECURITY.md](https://github.com/jsugg/ser/blob/main/SECURITY.md)
- Architecture decisions index: [docs/adr/README.md](https://github.com/jsugg/ser/blob/main/docs/adr/README.md)
- License: [LICENSE](https://github.com/jsugg/ser/blob/main/LICENSE)
Loading