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
44 changes: 44 additions & 0 deletions .github/scripts/tag-to-pep440.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
#
# Convert an agent-assembly release tag to a PEP 440 version string.
#
# Examples:
# v0.0.1 -> 0.0.1
# v0.0.1-alpha.8 -> 0.0.1a8
# v0.0.1-beta.2 -> 0.0.1b2
# v0.0.1-rc.3 -> 0.0.1rc3
#
# Usage (CLI):
# .github/scripts/tag-to-pep440.sh v0.0.1-alpha.8
#
# Usage (library — preferred from CI/workflows):
# source .github/scripts/tag-to-pep440.sh
# pypi_version="$(tag_to_pep440 "v0.0.1-alpha.8")"
#
# This is the single source of truth for the conversion. The
# release-python.yml `resolve` job sources this file so the test
# harness exercises the exact code path the workflow runs.
#
# Owner: python-sdk release pipeline (AAASM-2857, hardened in AAASM-2863).

# Conversion function. Reads the tag from $1 and prints the PEP 440 form
# to stdout. Returns non-zero only if the input is empty (sed itself is
# a pure transformation; unknown pre-release shapes are passed through
# unchanged so callers can layer their own validation).
tag_to_pep440() {
local tag="${1-}"
if [[ -z "$tag" ]]; then
echo "tag_to_pep440: missing tag argument" >&2
return 1
fi
local stripped="${tag#v}"
printf '%s\n' "$stripped" | sed -E 's/-alpha\.([0-9]+)/a\1/; s/-beta\.([0-9]+)/b\1/; s/-rc\.([0-9]+)/rc\1/'
}

# When executed directly (not sourced), behave as a CLI: take the tag
# as $1 and print the converted version. When sourced, define the
# function only and let the caller drive it.
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
set -euo pipefail
tag_to_pep440 "${1-}"
fi
110 changes: 110 additions & 0 deletions .github/scripts/test_tag_to_pep440.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
#
# Unit tests for `.github/scripts/tag-to-pep440.sh`.
#
# Exercises the conversion function that release-python.yml's `resolve`
# job sources to turn an agent-assembly release tag into a PEP 440
# version string. Because the workflow sources the same file, these
# tests cover the actual code path CI runs, not a copy of the regex.
#
# Run locally:
# bash .github/scripts/test_tag_to_pep440.sh
#
# Run from CI: see .github/workflows/release-python-conversion-test.yml.
#
# Exits 0 when every fixture passes, 1 otherwise. Prints a per-fixture
# OK/FAIL line plus a final pass/fail/total summary.
#
# Refs AAASM-2863.

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=.github/scripts/tag-to-pep440.sh
source "${SCRIPT_DIR}/tag-to-pep440.sh"

pass=0
fail=0
total=0

# assert_eq <label> <expected> <actual>
assert_eq() {
local label="$1"
local expected="$2"
local actual="$3"
total=$((total + 1))
if [[ "$expected" == "$actual" ]]; then
printf 'OK %-50s -> %s\n' "$label" "$actual"
pass=$((pass + 1))
else
printf 'FAIL %-50s expected=%q actual=%q\n' "$label" "$expected" "$actual" >&2
fail=$((fail + 1))
fi
}

# check <tag> <expected_pep440>
check() {
local tag="$1"
local expected="$2"
local actual
actual="$(tag_to_pep440 "$tag")"
assert_eq "$tag" "$expected" "$actual"
}

echo "== tag_to_pep440 fixtures =="

# AAASM-2863 acceptance criteria — six fixture categories from the
# ticket description, exact strings from the AC bullet list.

# Stable form (no pre-release suffix).
check "v0.0.1" "0.0.1"

# Alpha pre-release, single + double digit.
check "v0.0.1-alpha.1" "0.0.1a1"
check "v0.0.1-alpha.10" "0.0.1a10"

# Beta pre-release.
check "v0.0.1-beta.2" "0.0.1b2"

# Release candidate.
check "v0.0.1-rc.3" "0.0.1rc3"

# Multi-digit version components in the base version + a large
# pre-release counter — guards against accidental greedy matching
# clobbering the minor/patch numbers.
check "v1.23.456-alpha.789" "1.23.456a789"

# --- Extra coverage beyond the AC minimum ---
#
# These are not strictly required by the ticket but document the
# script's intended behaviour for edge cases the workflow may meet.

# A "v" prefix is stripped, even when there is no pre-release.
check "v10.20.30" "10.20.30"

# An unknown pre-release type passes through unchanged: the script
# is a pure transformation; PEP 440 validation lives in the workflow.
# Documenting this prevents anyone "fixing" the regex to swallow it.
check "v0.0.1-dev.1" "0.0.1-dev.1"

# Beta + rc with multi-digit counters mirror alpha.10 for symmetry.
check "v0.0.1-beta.42" "0.0.1b42"
check "v0.0.1-rc.99" "0.0.1rc99"

# Empty input is an error — the workflow relies on this to fail fast
# rather than emit an empty pypi_version.
echo "== empty-input guard =="
total=$((total + 1))
if err="$(tag_to_pep440 "" 2>&1 >/dev/null)"; then
printf 'FAIL empty input should have exited non-zero (stderr=%q)\n' "$err" >&2
fail=$((fail + 1))
else
printf 'OK empty input rejected (stderr=%q)\n' "$err"
pass=$((pass + 1))
fi

echo
echo "Summary: ${pass} passed, ${fail} failed, ${total} total"
if (( fail > 0 )); then
exit 1
fi
47 changes: 47 additions & 0 deletions .github/workflows/release-python-conversion-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Release-Python conversion tests

# Run the tag → PEP 440 conversion test harness whenever its inputs
# change: the script under test, the test itself, the release-python
# workflow that sources the script, or this workflow file. This keeps
# the matrix in ci.yaml unaffected while still gating the release
# pipeline's conversion logic on a real CI run.
#
# Refs AAASM-2863.

on:
push:
branches:
- master
paths:
- ".github/scripts/tag-to-pep440.sh"
- ".github/scripts/test_tag_to_pep440.sh"
- ".github/workflows/release-python.yml"
- ".github/workflows/release-python-conversion-test.yml"
pull_request:
branches:
- master
paths:
- ".github/scripts/tag-to-pep440.sh"
- ".github/scripts/test_tag_to_pep440.sh"
- ".github/workflows/release-python.yml"
- ".github/workflows/release-python-conversion-test.yml"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test-tag-to-pep440:
name: Run tag → PEP 440 fixture suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Lint conversion + test scripts
run: |
shellcheck .github/scripts/tag-to-pep440.sh
shellcheck -x .github/scripts/test_tag_to_pep440.sh
- name: Run fixture suite
run: bash .github/scripts/test_tag_to_pep440.sh
14 changes: 11 additions & 3 deletions .github/workflows/release-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ jobs:
pypi_version: ${{ steps.r.outputs.pypi_version }}
dry_run: ${{ steps.r.outputs.dry_run }}
steps:
# Need the working tree on disk so the resolve step can source the
# tag → PEP 440 conversion script (.github/scripts/tag-to-pep440.sh).
# That script is the single source of truth for the conversion,
# shared with the AAASM-2863 unit tests.
- uses: actions/checkout@v6
- id: r
env:
EVENT_NAME: ${{ github.event_name }}
Expand Down Expand Up @@ -79,9 +84,12 @@ jobs:
# Convert agent-assembly tag (e.g. v0.0.1-alpha.8) to PEP 440 form
# (0.0.1a8). Mirrors the sync-version-from-dispatch composite action
# so the repository_dispatch path produces identical wheel + version
# state to today.
stripped="${DISPATCH_PAYLOAD_TAG#v}"
pypi_version=$(echo "$stripped" | sed -E 's/-alpha\.([0-9]+)/a\1/; s/-beta\.([0-9]+)/b\1/; s/-rc\.([0-9]+)/rc\1/')
# state to today. The conversion lives in a sourceable script so
# the AAASM-2863 unit tests exercise the same code path this
# workflow runs (instead of a copy of the regex).
# shellcheck source=.github/scripts/tag-to-pep440.sh
source "${GITHUB_WORKSPACE}/.github/scripts/tag-to-pep440.sh"
pypi_version="$(tag_to_pep440 "$DISPATCH_PAYLOAD_TAG")"
# repository_dispatch is always a real publish — it is fired by
# agent-assembly after a real upstream release.
dry_run="false"
Expand Down