Skip to content

publish

publish #8

Workflow file for this run

# 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 }}