diff --git a/.github/workflows/arstack-studio-linux-appimage.yml b/.github/workflows/arstack-studio-linux-appimage.yml index 5a9004bb5..9b04d33a0 100644 --- a/.github/workflows/arstack-studio-linux-appimage.yml +++ b/.github/workflows/arstack-studio-linux-appimage.yml @@ -37,6 +37,11 @@ jobs: run: | set -euo pipefail test "$(git rev-parse HEAD)" = "$(git rev-parse "$TAG^{commit}")" + git fetch --no-tags origin main + git merge-base --is-ancestor "$TAG^{commit}" origin/main || { + echo "Release source is not integrated into main; refusing Linux package" >&2 + exit 1 + } gh release view "$TAG" --json isDraft,isPrerelease -q 'select(.isDraft==false and .isPrerelease==false)' > release-check.json test -s release-check.json @@ -133,6 +138,12 @@ jobs: chmod +x ARStack-Studio-linux-x86_64.AppImage APPIMAGE_EXTRACT_AND_RUN=1 QT_QPA_PLATFORM=offscreen \ xvfb-run -a ./ARStack-Studio-linux-x86_64.AppImage --check-reference-template + APPIMAGE_EXTRACT_AND_RUN=1 QT_QPA_PLATFORM=offscreen \ + xvfb-run -a ./ARStack-Studio-linux-x86_64.AppImage --check-p0-policy + if [ -n "${{ inputs.release_tag }}" ]; then + APPIMAGE_EXTRACT_AND_RUN=1 QT_QPA_PLATFORM=offscreen \ + xvfb-run -a ./ARStack-Studio-linux-x86_64.AppImage --check-firmware-contract + fi sha256sum ARStack-Studio-linux-x86_64.AppImage > ARStack-Studio-linux-x86_64.sha256 - name: Upload reviewed Linux artifacts @@ -168,6 +179,11 @@ jobs: run: | set -euo pipefail test "$(git rev-parse HEAD)" = "$(git rev-parse "$TAG^{commit}")" + git fetch --no-tags origin main + git merge-base --is-ancestor "$TAG^{commit}" origin/main || { + echo "Release source is not integrated into main; refusing Linux package" >&2 + exit 1 + } gh release view "$TAG" --json isDraft,isPrerelease -q 'select(.isDraft==false and .isPrerelease==false)' > release-check.json test -s release-check.json sha256sum -c ARStack-Studio-linux-x86_64.sha256 diff --git a/.github/workflows/arstack-studio-linux-auto-release.yml b/.github/workflows/arstack-studio-linux-auto-release.yml index e6afa5a82..131405d7a 100644 --- a/.github/workflows/arstack-studio-linux-auto-release.yml +++ b/.github/workflows/arstack-studio-linux-auto-release.yml @@ -3,6 +3,12 @@ name: Attach Linux AppImage On Stable Release on: release: types: [published] + workflow_dispatch: + inputs: + release_tag: + description: 'Published latest stable tag to package (manual fallback if release event was suppressed)' + required: true + type: string pull_request: paths: - '.github/workflows/arstack-studio-linux-auto-release.yml' @@ -14,7 +20,7 @@ permissions: jobs: dispatch-matched-linux-package: name: Build tested AppImage for newly published stable tag - if: ${{ github.event_name == 'release' && !github.event.release.draft && !github.event.release.prerelease }} + if: ${{ (github.event_name == 'release' && !github.event.release.draft && !github.event.release.prerelease) || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -22,31 +28,51 @@ jobs: id: eligibility env: GH_TOKEN: ${{ github.token }} - TAG: ${{ github.event.release.tag_name }} + TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }} run: | set -euo pipefail gh api "repos/$GITHUB_REPOSITORY/releases/tags/$TAG" > release.json + gh api "repos/$GITHUB_REPOSITORY/releases/latest" > latest.json python3 - <<'PY' - import json, os + import json,os,sys r=json.load(open('release.json')) - assert not r['draft'] and not r['prerelease'], 'Only stable release builds are eligible' - assets={a['name'] for a in r['assets']} - assert 'firmware-manifest.json' in assets, 'Matched firmware manifest not published yet' - assert any(n.lower().endswith('.bin') for n in assets), 'Matched board firmware not published yet' - available=any(n.endswith('.AppImage') for n in assets) + latest=json.load(open('latest.json')) + tag=os.environ['TAG'] + assert r['tag_name']==tag, 'GitHub release tag mismatch' + assert not r['draft'] and not r['prerelease'], 'Only published stable releases are eligible' + assert r['id']==latest['id'], 'Only the latest stable release may receive a Linux package' + # M3.H is frozen under its existing five-asset immutable acceptance record. + frozen_tag='processbus-sim-m3h-20261024' + if tag==frozen_tag: + print('SKIP: immutable historical M3.H stays unchanged; Linux ships with a future stable tag') + with open(os.environ['GITHUB_OUTPUT'],'a') as out: out.write('dispatch=false\n') + sys.exit(0) + assets={a['name']:a for a in r['assets']} + required={'firmware-manifest.json','SHA256SUMS.txt'} + assert required.issubset(assets), 'Missing signed-off firmware manifest or checksums' + assert any(n.lower().endswith('.bin') for n in assets), 'Board firmware not published' + assert len([n for n in assets if n.lower().endswith('setup.exe') and 'win' in n.lower()])==1, 'Missing unambiguous Windows setup' + assert any(n.lower().endswith('portable.zip') for n in assets), 'Windows portable package not published' + for n,a in assets.items(): + if n.lower().endswith(('setup.exe','.bin','portable.zip')): + assert (a.get('digest') or '').startswith('sha256:'), f'Unverified release asset: {n}' + linux='ARStack-Studio-linux-x86_64.AppImage' + checksum='ARStack-Studio-linux-x86_64.sha256' + assert (linux in assets)==(checksum in assets), 'Linux AppImage/checksum incomplete pair' + available=linux in assets with open(os.environ['GITHUB_OUTPUT'],'a') as out: out.write(f"dispatch={'false' if available else 'true'}\n") if available: - print('SKIP: release already contains a Linux AppImage') + print('PASS: latest stable release already contains Linux AppImage and checksum') else: - print('PASS: matching firmware assets published; ready to package Linux Studio') + print('PASS: latest stable Windows + firmware release is complete; start exact-tag Linux build') PY - name: Dispatch exact-tag Linux AppImage build, smoke and attach workflow if: ${{ steps.eligibility.outputs.dispatch == 'true' }} env: GH_TOKEN: ${{ github.token }} - TAG: ${{ github.event.release.tag_name }} + TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }} run: | gh workflow run arstack-studio-linux-appimage.yml \ --repo "$GITHUB_REPOSITORY" --ref main \