From a844c5964684aea1a2fa83070a3a850f0bcca020 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:16:43 +0530 Subject: [PATCH 1/3] ci(release): attach tested immutable artifacts after publication --- .../workflows/published-release-assets.yml | 116 ++++++++++++++++++ README.md | 13 ++ docs/release-process.md | 18 +-- 3 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/published-release-assets.yml diff --git a/.github/workflows/published-release-assets.yml b/.github/workflows/published-release-assets.yml new file mode 100644 index 0000000..a2081f9 --- /dev/null +++ b/.github/workflows/published-release-assets.yml @@ -0,0 +1,116 @@ +name: Published release assets + +on: + release: + types: [published] + +permissions: + contents: read + actions: read + +concurrency: + group: published-release-assets-${{ github.event.release.tag_name }} + cancel-in-progress: false + +jobs: + compatibility: + if: ${{ github.event.release.prerelease == false }} + name: ${{ matrix.base_cli.name }} / Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.13"] + base_cli: + - name: minimum-0.4.3 + spec: "==0.4.3" + - name: latest-supported + spec: ">=0.4.3,<0.5" + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + ref: ${{ github.event.release.tag_name }} + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python-version }} + - name: Verify the release tag matches VERSION + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: test "$(tr -d '[:space:]' < VERSION)" = "${RELEASE_TAG#v}" + - name: Build the released demo wheel + run: python -m pip wheel --no-deps --wheel-dir dist . + - name: Install the framework version under test + run: python -m pip install "base-cli${{ matrix.base_cli.spec }}" "pytest>=8,<9" + - name: Install the release wheel without source dependencies + run: python -m pip install --no-deps dist/*.whl + - name: Run installed-wheel consumer tests + run: python -m pytest -q + - name: Record the exact compatibility evidence + env: + PYTHON_VERSION: ${{ matrix.python-version }} + BASE_CLI_REQUEST: ${{ matrix.base_cli.spec }} + run: | + python - <<'PY' > compatibility.txt + from importlib.metadata import version + import os + + print(f"python={os.environ['PYTHON_VERSION']}") + print(f"base-cli-request={os.environ['BASE_CLI_REQUEST']}") + print(f"base-cli-installed={version('base-cli')}") + print(f"base-cli-demo-installed={version('base-cli-demo')}") + PY + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: compatibility-${{ matrix.base_cli.name }}-python-${{ matrix.python-version }} + path: compatibility.txt + if-no-files-found: error + + assets: + if: ${{ github.event.release.prerelease == false }} + needs: [compatibility] + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + actions: read + contents: write + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + ref: ${{ github.event.release.tag_name }} + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.13" + - name: Verify the release tag matches VERSION + run: test "$(tr -d '[:space:]' < VERSION)" = "${RELEASE_TAG#v}" + - name: Install package and release checks + run: python -m pip install ".[dev]" + - name: Run the authoritative consumer and package gates + run: | + ./tests/validate.sh + ./tests/package.sh + - name: Build the release distributions + run: python -m build --sdist --wheel --outdir release-dist . + - name: Verify distribution metadata + run: python -m twine check release-dist/* + - name: Install and test the built release wheel + run: | + python -m pip install --no-deps release-dist/*.whl + python -m pytest -q + - name: Download compatibility evidence from this workflow run + run: gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" --pattern 'compatibility-*' --dir release-dist/compatibility-evidence + - name: Prepare evidence and immutable checksums + run: | + { + printf 'Release tag: %s\n' "$RELEASE_TAG" + printf 'Declared Base-CLI range: base-cli>=0.4.3,<0.5\n\n' + find release-dist/compatibility-evidence -name compatibility.txt -print -exec cat {} \; + } > release-dist/COMPATIBILITY.txt + sha256sum release-dist/*.whl release-dist/*.tar.gz release-dist/COMPATIBILITY.txt > release-dist/SHA256SUMS.txt + - name: Attach release distributions, compatibility evidence, and checksums + run: gh release upload "$RELEASE_TAG" release-dist/*.whl release-dist/*.tar.gz release-dist/COMPATIBILITY.txt release-dist/SHA256SUMS.txt --repo "$GITHUB_REPOSITORY" diff --git a/README.md b/README.md index f6993ca..5d3d52a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,19 @@ command tree, domain policy, and local data model. Northstar does not require Base, Docker, cloud credentials, or network access after its dependencies are installed. +## Install the released demo + +For the first published demo release, install the immutable wheel directly: + +```bash +python -m pip install \ + "https://github.com/basefoundry/base-cli-demo/releases/download/v0.1.0/base_cli_demo-0.1.0-py3-none-any.whl" +``` + +Verify the artifact against `SHA256SUMS.txt` on the same GitHub Release. The +wheel depends on the released `base-cli>=0.4.3,<0.5` API line; installing the +demo does not install Base or require a Base workspace. + ## Quick start From a fresh checkout: diff --git a/docs/release-process.md b/docs/release-process.md index 8ba3069..a36755b 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -35,17 +35,21 @@ Releases. ``` 8. Verify the annotated tag and GitHub Release for `basefoundry/base-cli-demo`. -9. The `Release package` workflow validates the tagged version, runs the - package gate, installs the built wheel, and uploads the wheel/source - distributions as a workflow artifact. Base's `basectl release publish` - remains the guarded publisher for GitHub Release notes; this demo workflow - does not imply a PyPI or Base-CLI release. -10. Complete every declared downstream handoff. For Homebrew, update the tap +9. After an explicitly authorized GitHub Release is published, the + `Published release assets` workflow tests the demo wheel against the minimum + and latest supported Base-CLI releases on Python 3.10 and 3.13. Only after + those checks pass does it attach the wheel, source distribution, exact + compatibility evidence, and SHA-256 checksums. It never creates a tag or + GitHub Release and does not imply a PyPI or Base-CLI release. +10. Confirm the README's version-pinned installation path in a clean + environment, download the checksum/evidence files, and verify package + filenames and hashes against the published assets. +11. Complete every declared downstream handoff. For Homebrew, update the tap formula to the published archive and checksum, run the formula tests and audit, publish required bottles, and verify install and upgrade paths. If a downstream repository pins this project by commit, update and validate that pin after the release. -11. Record the release and downstream URLs on the release issue, then remove +12. Record the release and downstream URLs on the release issue, then remove the release worktree and merged branches when safe. ## Repository Contract From c43893512b589fd7c054a41b7e8928c7a8eda9ae Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:19:53 +0530 Subject: [PATCH 2/3] docs(release): keep the stable install path in quick start --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5d3d52a..993fd85 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,6 @@ command tree, domain policy, and local data model. Northstar does not require Base, Docker, cloud credentials, or network access after its dependencies are installed. -## Install the released demo - -For the first published demo release, install the immutable wheel directly: - -```bash -python -m pip install \ - "https://github.com/basefoundry/base-cli-demo/releases/download/v0.1.0/base_cli_demo-0.1.0-py3-none-any.whl" -``` - -Verify the artifact against `SHA256SUMS.txt` on the same GitHub Release. The -wheel depends on the released `base-cli>=0.4.3,<0.5` API line; installing the -demo does not install Base or require a Base workspace. - ## Quick start From a fresh checkout: @@ -34,6 +21,19 @@ $ northstar --help $ northstar --quiet status ``` +### Install an existing release + +To install the first published demo release without cloning the repository: + +```bash +python -m pip install \ + "https://github.com/basefoundry/base-cli-demo/releases/download/v0.1.0/base_cli_demo-0.1.0-py3-none-any.whl" +``` + +Verify the artifact against `SHA256SUMS.txt` on the same GitHub Release. The +wheel depends on the released `base-cli>=0.4.3,<0.5` API line; it does not +install Base or require a Base workspace. + The default environment is `dev`. Select another fixture environment with the framework lifecycle option: From 8b64b5c20579a76969509767b5bf410fcb7700eb Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:30:01 +0530 Subject: [PATCH 3/3] ci(release): stage assets on a draft before publication --- .../workflows/published-release-assets.yml | 38 +++++++++++------- docs/release-process.md | 39 ++++++++++++------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/.github/workflows/published-release-assets.yml b/.github/workflows/published-release-assets.yml index a2081f9..2133009 100644 --- a/.github/workflows/published-release-assets.yml +++ b/.github/workflows/published-release-assets.yml @@ -1,23 +1,32 @@ -name: Published release assets +name: Prepare draft release assets on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: Existing version tag for the draft release, for example v0.1.0 + required: true + type: string + draft_release_id: + description: Numeric GitHub release ID of the unpublished draft for that tag + required: true + type: string permissions: contents: read actions: read concurrency: - group: published-release-assets-${{ github.event.release.tag_name }} + group: draft-release-assets-${{ inputs.tag }} cancel-in-progress: false jobs: compatibility: - if: ${{ github.event.release.prerelease == false }} name: ${{ matrix.base_cli.name }} / Python ${{ matrix.python-version }} runs-on: ubuntu-latest timeout-minutes: 20 + env: + RELEASE_TAG: ${{ inputs.tag }} strategy: fail-fast: false matrix: @@ -30,14 +39,12 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: - ref: ${{ github.event.release.tag_name }} + ref: ${{ inputs.tag }} - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Verify the release tag matches VERSION - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} run: test "$(tr -d '[:space:]' < VERSION)" = "${RELEASE_TAG#v}" - name: Build the released demo wheel run: python -m pip wheel --no-deps --wheel-dir dist . @@ -68,7 +75,6 @@ jobs: if-no-files-found: error assets: - if: ${{ github.event.release.prerelease == false }} needs: [compatibility] runs-on: ubuntu-latest timeout-minutes: 20 @@ -76,18 +82,24 @@ jobs: actions: read contents: write env: - RELEASE_TAG: ${{ github.event.release.tag_name }} + RELEASE_TAG: ${{ inputs.tag }} + DRAFT_RELEASE_ID: ${{ inputs.draft_release_id }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: - ref: ${{ github.event.release.tag_name }} + ref: ${{ inputs.tag }} - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" - name: Verify the release tag matches VERSION run: test "$(tr -d '[:space:]' < VERSION)" = "${RELEASE_TAG#v}" + - name: Verify the selected release is an unpublished draft for this tag + run: | + test "$(gh api "repos/$GITHUB_REPOSITORY/releases/$DRAFT_RELEASE_ID" --jq '.draft')" = true + test "$(gh api "repos/$GITHUB_REPOSITORY/releases/$DRAFT_RELEASE_ID" --jq '.prerelease')" = false + test "$(gh api "repos/$GITHUB_REPOSITORY/releases/$DRAFT_RELEASE_ID" --jq '.tag_name')" = "$RELEASE_TAG" - name: Install package and release checks run: python -m pip install ".[dev]" - name: Run the authoritative consumer and package gates @@ -112,5 +124,5 @@ jobs: find release-dist/compatibility-evidence -name compatibility.txt -print -exec cat {} \; } > release-dist/COMPATIBILITY.txt sha256sum release-dist/*.whl release-dist/*.tar.gz release-dist/COMPATIBILITY.txt > release-dist/SHA256SUMS.txt - - name: Attach release distributions, compatibility evidence, and checksums - run: gh release upload "$RELEASE_TAG" release-dist/*.whl release-dist/*.tar.gz release-dist/COMPATIBILITY.txt release-dist/SHA256SUMS.txt --repo "$GITHUB_REPOSITORY" + - name: Attach verified assets to the unpublished draft + run: gh release upload "$RELEASE_TAG" release-dist/*.whl release-dist/*.tar.gz release-dist/COMPATIBILITY.txt release-dist/SHA256SUMS.txt --clobber --repo "$GITHUB_REPOSITORY" diff --git a/docs/release-process.md b/docs/release-process.md index a36755b..c0d17a9 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -1,9 +1,9 @@ # Release Process This repository uses the Base release contract. The machine-readable release -metadata lives in `base_manifest.yaml`; the guarded `basectl release` -commands use that contract for readiness checks, notes, tags, and GitHub -Releases. +metadata lives in `base_manifest.yaml`; `basectl release check/plan/notes` use +that contract for readiness and release notes. GitHub immutable releases must +be prepared as drafts so validated assets are attached before publication. ## Standard Sequence @@ -23,24 +23,35 @@ Releases. ```bash basectl release check --version X.Y.Z basectl release plan --version X.Y.Z - basectl release notes --version X.Y.Z + basectl release notes --version X.Y.Z > RELEASE_NOTES.md basectl release publish --version X.Y.Z --dry-run ``` -7. Publish only after the checks pass. Use `--yes` only from a trusted - non-interactive release shell: +7. After separate publication authorization, verify immutable releases are + enabled for the repository, then create the annotated version tag for the + exact reviewed `main` commit. Create a GitHub Release draft for that existing + tag and add the release notes from step 6 (`RELEASE_NOTES.md`). Do not run + `basectl release publish --yes` for this immutable-release path: it creates + a published release directly, leaving no draft stage for attaching the + validated assets. ```bash - basectl release publish --version X.Y.Z --yes + git tag -a vX.Y.Z -m "base-cli-demo vX.Y.Z" + git push origin vX.Y.Z + gh release create vX.Y.Z --verify-tag --draft --title "vX.Y.Z" --notes-file RELEASE_NOTES.md ``` -8. Verify the annotated tag and GitHub Release for `basefoundry/base-cli-demo`. -9. After an explicitly authorized GitHub Release is published, the - `Published release assets` workflow tests the demo wheel against the minimum - and latest supported Base-CLI releases on Python 3.10 and 3.13. Only after - those checks pass does it attach the wheel, source distribution, exact - compatibility evidence, and SHA-256 checksums. It never creates a tag or - GitHub Release and does not imply a PyPI or Base-CLI release. +8. Dispatch `Prepare draft release assets` with the tag and draft release ID + (`gh release view vX.Y.Z --json databaseId --jq .databaseId`). The workflow + verifies that the selected release is still a draft for that tag, tests the + wheel against the minimum and latest supported Base-CLI releases on Python + 3.10 and 3.13, then attaches the wheel, sdist, exact compatibility evidence, + and SHA-256 checksums. It never creates a tag or release and never publishes + the draft. +9. Review the draft assets and verify the repository has immutable releases + enabled. Then publish the draft in GitHub. When immutable releases are + enabled, GitHub locks the tag and assets at publication; see the official + [immutable releases guidance](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases). 10. Confirm the README's version-pinned installation path in a clean environment, download the checksum/evidence files, and verify package filenames and hashes against the published assets.