publish #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright (c) 2026 [Ribose Inc](https://www.ribose.com). | |
| # All rights reserved. | |
| # This file is a part of tamatebako | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions | |
| # are met: | |
| # 1. Redistributions of source code must retain the above copyright | |
| # notice, this list of conditions and the following disclaimer. | |
| # 2. Redistributions in binary form must reproduce the above copyright | |
| # notice, this list of conditions and the following disclaimer in the | |
| # documentation and/or other materials provided with the distribution. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
| # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS | |
| # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| # POSSIBILITY OF SUCH DAMAGE. | |
| # The release coordinator (tebako-runtime-ruby's publish.yml grammar). | |
| # The explicit, operator-facing entry point — the three first-class slice | |
| # shapes: | |
| # one python on every platform (python_filter=<version>, platform=all) | |
| # one platform, every python (python_filter=full|tidy|catalog, platform=<one>) | |
| # one python on one platform (python_filter=<version>, platform=<one>) | |
| # plus the era baseline (python_filter=catalog, platform=all). | |
| # | |
| # Every slice fans out to _build-platform.yml to BUILD, and — spec 13 | |
| # §2a's de-rendezvous (tebako-runtime-ruby#161's pattern) — to PUBLISH | |
| # and SIGN in-leg: the leg that built a package uploads its write-once | |
| # names (payload + .sha256 sidecars + .manifest.json shard) and signs | |
| # every served name, all inside the leg that holds the fresh bytes. No | |
| # shared mutable file exists anymore (the monolithic manifest.json / | |
| # SHA256SUMS.txt are gone — `tebako-pkg release-index` derives them | |
| # consumer-side), so N legs publish concurrently with zero rendezvous and | |
| # one failed leg no longer blocks the green legs' packages. What remains | |
| # here is the ONE release job per coordinator run: it AUDITS the whole | |
| # matrix against the release (read-only) — the one duty that genuinely | |
| # needs the full matrix. | |
| # | |
| # Deliberate deviations from the ruby coordinator: | |
| # - publish defaults to FALSE (ruby: true): publishing is an explicit | |
| # operator act. | |
| # - No repository_dispatch trigger yet: the python source factory does | |
| # not dispatch "tebako release" here (the ruby chain's pin-moved | |
| # rebuilds). The trigger arrives when tamatebako/python wires it. | |
| # - The release job authenticates with the runner GITHUB_TOKEN, not the | |
| # ruby grammar's `TEBAKO_CI_PAT_TOKEN || GITHUB_TOKEN` preference: | |
| # the org PAT's fine-grained repository selection predates this repo | |
| # and 404s every API call from here (the first publish died at | |
| # create_release, run 33999688520). The runner token's contents:write | |
| # suffices — no downstream cascade listens to this factory's releases | |
| # yet. The PAT preference returns WITH the cascade wiring, and the | |
| # PAT's repo selection must cover this repo by then | |
| # (tebako-runtime-python#5 — owner action). The legs' in-leg | |
| # publish/sign steps ride the same runner token. | |
| # - No registry render/PR step in the release job: this factory has no | |
| # tpkg-registry.yaml mirror yet (the ruby coordinator's second | |
| # release-job duty); when the mirror arrives the step ports with it. | |
| name: publish | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| python_filter: | |
| description: 'Pythons: "full" (line tips), "tidy", "catalog" (every published version), or comma-separated versions' | |
| type: string | |
| default: 'full' | |
| platform: | |
| description: 'Platform slice: "all" or one of windows | linux-gnu | linux-musl | macos' | |
| type: string | |
| default: 'all' | |
| arch_filter: | |
| description: 'Optional arch slice within each platform (x86_64 | arm64)' | |
| type: string | |
| default: 'all' | |
| publish: | |
| description: 'Publish this run''s packages to the release' | |
| type: boolean | |
| default: false | |
| force_rebuild: | |
| description: 'Re-upload even unchanged assets (the one exception to per-name byte-immutability; the ruby coordinator declares this input but never wires it — here it reaches upload_release.rb as FORCE_REBUILD)' | |
| type: boolean | |
| default: false | |
| audit: | |
| description: 'Audit only: verify the release against the expected matrix, no builds/uploads' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| jobs: | |
| windows: | |
| if: ${{ inputs.platform == 'all' || inputs.platform == 'windows' }} | |
| uses: ./.github/workflows/_build-platform.yml | |
| with: | |
| platform: windows | |
| python_filter: ${{ inputs.python_filter }} | |
| arch_filter: ${{ inputs.arch_filter }} | |
| audit: ${{ inputs.audit }} | |
| publish: ${{ inputs.publish }} | |
| force_rebuild: ${{ inputs.force_rebuild }} | |
| secrets: inherit | |
| linux-gnu: | |
| if: ${{ inputs.platform == 'all' || inputs.platform == 'linux-gnu' }} | |
| uses: ./.github/workflows/_build-platform.yml | |
| with: | |
| platform: linux-gnu | |
| python_filter: ${{ inputs.python_filter }} | |
| arch_filter: ${{ inputs.arch_filter }} | |
| audit: ${{ inputs.audit }} | |
| publish: ${{ inputs.publish }} | |
| force_rebuild: ${{ inputs.force_rebuild }} | |
| secrets: inherit | |
| linux-musl: | |
| if: ${{ inputs.platform == 'all' || inputs.platform == 'linux-musl' }} | |
| uses: ./.github/workflows/_build-platform.yml | |
| with: | |
| platform: linux-musl | |
| python_filter: ${{ inputs.python_filter }} | |
| arch_filter: ${{ inputs.arch_filter }} | |
| audit: ${{ inputs.audit }} | |
| publish: ${{ inputs.publish }} | |
| force_rebuild: ${{ inputs.force_rebuild }} | |
| secrets: inherit | |
| macos: | |
| if: ${{ inputs.platform == 'all' || inputs.platform == 'macos' }} | |
| uses: ./.github/workflows/_build-platform.yml | |
| with: | |
| platform: macos | |
| python_filter: ${{ inputs.python_filter }} | |
| arch_filter: ${{ inputs.arch_filter }} | |
| audit: ${{ inputs.audit }} | |
| publish: ${{ inputs.publish }} | |
| force_rebuild: ${{ inputs.force_rebuild }} | |
| secrets: inherit | |
| # The ONE release job per coordinator run. Spec 13 §2a's de-rendezvous | |
| # moved the publish into the legs (write-once per-leg names — no shared | |
| # release-asset mutation remains); what serializes here is the one duty | |
| # that needs the whole matrix: the AUDIT (every platform's expected | |
| # names verified against the release, read-only). A runner that dies | |
| # mid-audit never reports, and the job-level group | |
| # (cancel-in-progress: false) then holds every later audit hostage for | |
| # the cap — bound it. | |
| release: | |
| name: Audit the release | |
| needs: [windows, linux-gnu, linux-musl, macos] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| concurrency: | |
| group: publish-runtime-packages | |
| cancel-in-progress: false | |
| # Run when the run asked to publish or audit and at least one | |
| # platform actually built (an audit builds nothing but still computes | |
| # every selected platform's expectations). | |
| if: ${{ always() && !cancelled() && (inputs.publish || inputs.audit) && (needs.windows.result == 'success' || needs.linux-gnu.result == 'success' || needs.linux-musl.result == 'success' || needs.macos.result == 'success') }} | |
| env: | |
| # Runner GITHUB_TOKEN, not the org PAT preference — see the header's | |
| # deviation note and tebako-runtime-python#5. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup Ruby gems | |
| uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 | |
| with: | |
| ruby-version: '3.3' | |
| bundler-cache: true | |
| # The audit, per platform that built: the legs already published | |
| # their packages in-leg, so this job never downloads a byte — it | |
| # verifies each platform's expected names (payloads, .sha256 | |
| # sidecars, shards, and — on signing-enabled lines — every .asc, | |
| # spec 09 §5) against the release's own listing, read-only. | |
| - name: Audit the release, per platform | |
| env: | |
| AUDIT_ONLY: "true" | |
| TEBAKO_RELEASE_SIGNING_ENABLED: ${{ vars.TEBAKO_RELEASE_SIGNING_ENABLED }} | |
| WIN_RUN: ${{ needs.windows.outputs.run }} | |
| WIN_VER: ${{ needs.windows.outputs.tebako_version }} | |
| WIN_ENV: ${{ needs.windows.outputs.env_matrix }} | |
| WIN_PYTHON: ${{ needs.windows.outputs.python_matrix }} | |
| GNU_RUN: ${{ needs.linux-gnu.outputs.run }} | |
| GNU_VER: ${{ needs.linux-gnu.outputs.tebako_version }} | |
| GNU_ENV: ${{ needs.linux-gnu.outputs.env_matrix }} | |
| GNU_PYTHON: ${{ needs.linux-gnu.outputs.python_matrix }} | |
| MUSL_RUN: ${{ needs.linux-musl.outputs.run }} | |
| MUSL_VER: ${{ needs.linux-musl.outputs.tebako_version }} | |
| MUSL_ENV: ${{ needs.linux-musl.outputs.env_matrix }} | |
| MUSL_PYTHON: ${{ needs.linux-musl.outputs.python_matrix }} | |
| MAC_RUN: ${{ needs.macos.outputs.run }} | |
| MAC_VER: ${{ needs.macos.outputs.tebako_version }} | |
| MAC_ENV: ${{ needs.macos.outputs.env_matrix }} | |
| MAC_PYTHON: ${{ needs.macos.outputs.python_matrix }} | |
| run: | | |
| set -uo pipefail | |
| # Audit every platform that built green; THEN fail loudly naming | |
| # any selected platform that did not — a green platform's audit | |
| # always runs, and a coverage gap never goes unreported. | |
| audit_failed="" | |
| audit_platform() { | |
| local run="$1" ver="$2" envm="$3" pym="$4" name="$5" | |
| if [ "$run" != "true" ]; then | |
| echo "--- $name did not build this run; skipping its audit" | |
| return 0 | |
| fi | |
| echo "=== auditing $name (tebako $ver)" | |
| TEBAKO_VERSION="$ver" EXPECTED_ENV_MATRIX="$envm" EXPECTED_PYTHON_MATRIX="$pym" \ | |
| ./scripts/upload_release.rb || audit_failed="$audit_failed $name" | |
| } | |
| audit_platform "$WIN_RUN" "$WIN_VER" "$WIN_ENV" "$WIN_PYTHON" windows | |
| audit_platform "$GNU_RUN" "$GNU_VER" "$GNU_ENV" "$GNU_PYTHON" linux-gnu | |
| audit_platform "$MUSL_RUN" "$MUSL_VER" "$MUSL_ENV" "$MUSL_PYTHON" linux-musl | |
| audit_platform "$MAC_RUN" "$MAC_VER" "$MAC_ENV" "$MAC_PYTHON" macos | |
| failed="" | |
| mark() { # $1=selected? $2=result $3=name | |
| if [ "$1" = "true" ] && [ "$2" != "success" ] && [ "$2" != "skipped" ]; then | |
| failed="$failed $3" | |
| fi | |
| } | |
| mark "${{ inputs.platform == 'all' || inputs.platform == 'windows' }}" "${{ needs.windows.result }}" windows | |
| mark "${{ inputs.platform == 'all' || inputs.platform == 'linux-gnu' }}" "${{ needs.linux-gnu.result }}" linux-gnu | |
| mark "${{ inputs.platform == 'all' || inputs.platform == 'linux-musl' }}" "${{ needs.linux-musl.result }}" linux-musl | |
| mark "${{ inputs.platform == 'all' || inputs.platform == 'macos' }}" "${{ needs.macos.result }}" macos | |
| if [ -n "$failed" ]; then | |
| echo "::error::these platforms were selected but did not build green — their packages did not land:$failed" | |
| fi | |
| if [ -n "$audit_failed" ]; then | |
| echo "::error::these platforms' audits FAILED — the release is incomplete:$audit_failed" | |
| fi | |
| [ -z "$failed" ] && [ -z "$audit_failed" ] | |
| - name: Ping the versions catalog | |
| if: ${{ always() && success() }} | |
| continue-on-error: true | |
| # Refreshes www.tebako.org/versions within minutes of a publish | |
| # (versions plan 05; the daily cron stays the backstop). The org | |
| # TEBAKO_CI_PAT_TOKEN must carry actions:write on | |
| # tamatebako/versions for the dispatch — until proven, a failed | |
| # ping never blocks a release. | |
| run: gh api repos/tamatebako/versions/dispatches -f event_type=runtime-published | |
| env: | |
| GH_TOKEN: ${{ secrets.TEBAKO_CI_PAT_TOKEN || secrets.GITHUB_TOKEN }} |