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
61 changes: 58 additions & 3 deletions .github/workflows/release-and-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ name: Release and PyPI Publish
# - build-and-attest: can only read source and issue artifact attestations;
# - publish-pypi: receives only the project-scoped PyPI token;
# - publish-github: may only attach assets to the GitHub Release.
# - reconcile-platform-manifest: updates main only after PyPI exposes the exact
# released bytes, then validates the complete platform contract.
#
# Every third-party action is pinned to a full commit SHA. Dependabot should
# propose reviewed SHA updates rather than following mutable tags.
Expand Down Expand Up @@ -155,14 +157,66 @@ jobs:
github_token: ${{ github.token }}
tag: ${{ needs.release.outputs.tag }}

reconcile-platform-manifest:
needs: [release, publish-pypi]
if: >-
${{ needs.release.outputs.released == 'true' &&
needs.publish-pypi.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout released main
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
fetch-depth: 0
token: ${{ secrets.ADMIN_TOKEN }}

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- name: Regenerate and validate the published platform manifest
run: |
for attempt in $(seq 1 12); do
if python scripts/generate_platform_manifest.py; then
break
fi
if [ "$attempt" -eq 12 ]; then
echo "PyPI did not expose the released launcher artifacts in time." >&2
exit 1
fi
echo "Published artifacts are not visible yet; retrying in 15 seconds."
sleep 15
done
python scripts/validate_platform_manifest.py
python scripts/validate_platform_manifest.py --offline

- name: Commit the reconciled manifest
run: |
if git diff --quiet -- platform-manifest.json; then
echo "platform-manifest.json already matches the published release."
exit 0
fi
git config user.name "OpenAdapt Bot"
git config user.email "bot@openadapt.ai"
git add platform-manifest.json
git commit -m "chore(release): reconcile platform manifest"
git push origin HEAD:main

report-release-failure:
needs: [release, build-and-attest, publish-pypi, publish-github]
needs:
[release, build-and-attest, publish-pypi, publish-github, reconcile-platform-manifest]
if: >-
${{ always() &&
(needs.release.result == 'failure' ||
needs.build-and-attest.result == 'failure' ||
needs.publish-pypi.result == 'failure' ||
needs.publish-github.result == 'failure') }}
needs.publish-github.result == 'failure' ||
needs.reconcile-platform-manifest.result == 'failure') }}
runs-on: ubuntu-latest
permissions:
issues: write
Expand All @@ -175,11 +229,12 @@ jobs:
BUILD_RESULT: ${{ needs.build-and-attest.result }}
PYPI_RESULT: ${{ needs.publish-pypi.result }}
GITHUB_RESULT: ${{ needs.publish-github.result }}
MANIFEST_RESULT: ${{ needs.reconcile-platform-manifest.result }}
run: |
TITLE="Release workflow failed on main"
BODY="The release workflow failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

release=${RELEASE_RESULT}, build-and-attest=${BUILD_RESULT}, publish-pypi=${PYPI_RESULT}, publish-github=${GITHUB_RESULT}.
release=${RELEASE_RESULT}, build-and-attest=${BUILD_RESULT}, publish-pypi=${PYPI_RESULT}, publish-github=${GITHUB_RESULT}, reconcile-platform-manifest=${MANIFEST_RESULT}.

Until this is fixed, do not assume that the Git tag, PyPI files, GitHub assets, and attestations are mutually complete."
EXISTING=$(gh issue list --repo "${{ github.repository }}" --state open --search "in:title \"$TITLE\"" --json number --jq '.[0].number // empty')
Expand Down
10 changes: 9 additions & 1 deletion docs/platform-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ it fails loudly. If a launcher release train is in flight (pyproject.toml
ahead of PyPI), pass `--allow-unreleased-launcher`; the manifest still records
the published version.

Regenerate the manifest after each component release and commit the result.
The launcher release workflow regenerates and validates the manifest after the
new launcher artifacts are visible on PyPI, then commits the exact published
URLs and digests back to `main`. This ordering is intentional: the manifest
cannot truthfully name a launcher release before its immutable artifacts exist.
If publication or reconciliation fails, `main` remains red and the release
workflow opens or updates a failure issue rather than weakening validation.

Regenerate and commit the manifest manually after other component releases;
the scheduled validator catches component or public-status drift.

## How it is validated

Expand Down
16 changes: 8 additions & 8 deletions platform-manifest.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
{
"manifest_kind": "openadapt-platform-release-manifest",
"schema_version": "1.0.0",
"generated_at": "2026-07-26T19:20:22+00:00",
"generated_at": "2026-07-26T20:55:04+00:00",
"release_channel": "beta",
"components": {
"launcher": {
"package": "openadapt",
"version": "1.7.3",
"version": "1.8.0",
"source": "pypi",
"requires_python": "<3.13,>=3.10",
"artifacts": [
{
"type": "bdist_wheel",
"filename": "openadapt-1.7.3-py3-none-any.whl",
"url": "https://files.pythonhosted.org/packages/08/58/990f7f31360b0be723e004271b1e7016708c21627b1910f3bef751e11f45/openadapt-1.7.3-py3-none-any.whl",
"sha256": "f173f1454802bc01c966f973755d6657af9b968cbc0318483d75751adc79b9d0"
"filename": "openadapt-1.8.0-py3-none-any.whl",
"url": "https://files.pythonhosted.org/packages/23/50/84edb7de7fb4046a1cfe4829a7bf65f392fcb1d7f547065dc733624b9c4d/openadapt-1.8.0-py3-none-any.whl",
"sha256": "4342fa0b48f790ee43b525fd440ccdfc6a4fa95b075d2c03eac695faa80c1cd5"
},
{
"type": "sdist",
"filename": "openadapt-1.7.3.tar.gz",
"url": "https://files.pythonhosted.org/packages/d1/f8/fed8b98bdc39376e7bd1aa9cccba22a415f54eb225b1f262d8126d9353d7/openadapt-1.7.3.tar.gz",
"sha256": "5a7d63bfacea0b579d06d48886348ce0bce38db7c55e04839ea9d860c36e6d79"
"filename": "openadapt-1.8.0.tar.gz",
"url": "https://files.pythonhosted.org/packages/a4/6b/f62cc892b1e37f28acf2824b8b5b9316e98e87aa6de303b1b6a8538bdccc/openadapt-1.8.0.tar.gz",
"sha256": "87d278e1be51f2b9b5a64fcbdd3f7ad33097c135c9dfdb916c529cd77f5eeb80"
}
]
},
Expand Down