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
15 changes: 13 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Weekly — catches newly added queries against unchanged code.
- cron: "0 6 * * 1"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
analyze:
name: Analyze (Python)
Expand All @@ -23,12 +34,12 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@8272c299f21ca24af15dfe9ac0971ba969e5e0d5 # v3.36.2
with:
languages: python
queries: security-and-quality

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@8272c299f21ca24af15dfe9ac0971ba969e5e0d5 # v3.36.2
with:
category: "/language:python"
112 changes: 112 additions & 0 deletions .github/workflows/create-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
##
## SPDX-FileCopyrightText: 2026 AOT Technologies
## SPDX-License-Identifier: Apache-2.0
##

name: Create Release Tag

# Step 1 of the release flow. Run this first, then run "GitHub Release".
# Go to Actions → "Create Release Tag" → Run workflow with the target version.
on:
workflow_dispatch:
inputs:
version:
description: "Semver version to tag, without leading v (for example, 1.0.0)"
required: true
type: string

permissions:
contents: write

jobs:
create-tag:
name: Validate and tag main
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main
fetch-depth: 0

- name: Validate version format
shell: python
run: |
import re
import sys

version = "${{ inputs.version }}".strip()
if not re.fullmatch(r"\d+\.\d+\.\d+", version):
print(f"ERROR: {version!r} is not a MAJOR.MINOR.PATCH version", file=sys.stderr)
sys.exit(1)

print(f"Version {version} is valid semver")

- name: Check tag does not already exist
run: |
TAG="v${{ inputs.version }}"
if git rev-parse --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
echo "ERROR: tag ${TAG} already exists" >&2
exit 1
fi
echo "Tag ${TAG} is available"

- name: Validate package versions match
shell: python
run: |
import pathlib
import sys
import tomllib

version = "${{ inputs.version }}".strip()
root = pathlib.Path(".")
pyprojects = [root / "pyproject.toml", *sorted(root.glob("packages/**/pyproject.toml"))]

mismatches = []
for path in pyprojects:
data = tomllib.loads(path.read_text(encoding="utf-8"))
actual = data.get("project", {}).get("version")
if actual != version:
mismatches.append(f"{path}: expected {version}, found {actual}")

if mismatches:
print("ERROR: package versions do not match the requested tag", file=sys.stderr)
for mismatch in mismatches:
print(f" {mismatch}", file=sys.stderr)
sys.exit(1)

print(f"PASS: all packages are at version {version}")

- name: Validate CHANGELOG entry exists
shell: python
run: |
import pathlib
import re
import sys

version = "${{ inputs.version }}".strip()
changelog = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8")

heading = re.compile(
rf"^## \[{re.escape(version)}\] - \d{{4}}-\d{{2}}-\d{{2}}\s*$",
re.MULTILINE,
)
if not heading.search(changelog):
print(f"ERROR: CHANGELOG.md is missing a dated [{version}] section", file=sys.stderr)
sys.exit(1)

link_pattern = rf"^\[{re.escape(version)}\]: .+/releases/tag/v{re.escape(version)}\s*$"
if not re.search(link_pattern, changelog, re.MULTILINE):
print(f"ERROR: CHANGELOG.md is missing the [{version}] release link", file=sys.stderr)
sys.exit(1)

print(f"PASS: CHANGELOG.md has a valid [{version}] entry")

- name: Create and push tag
run: |
TAG="v${{ inputs.version }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "${TAG}" -m "Release ${TAG}"
git push origin "${TAG}"
echo "Tagged $(git rev-parse HEAD) as ${TAG}"
2 changes: 1 addition & 1 deletion .github/workflows/dco.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
github.actor != 'renovate[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/docker-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ name: Docker policy

on:
push:
branches: [main, master]
branches: [main]
paths:
- "Dockerfile"
- "docker/**"
Expand All @@ -17,6 +17,13 @@ on:
- "Dockerfile"
- "docker/**"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
check:
runs-on: ubuntu-latest
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/github-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ jobs:

- name: Resolve release version
shell: python
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
import os
import re
import sys

version = "${{ inputs.version }}".strip()
version = os.environ["INPUT_VERSION"].strip()
tag = f"v{version}"

if not re.fullmatch(r"\d+\.\d+\.\d+", version):
Expand All @@ -56,12 +58,12 @@ jobs:
run: git rev-parse --verify "refs/tags/${RELEASE_TAG}"

- name: Set up Python
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.11"

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
enable-cache: true
cache-dependency-glob: |
Expand Down
33 changes: 20 additions & 13 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,26 @@ name: Lint and Type Check

on:
pull_request:
branches: [ "main" ]
branches: [main]
push:
branches: [main, master]
branches: [main]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lockfile-check:
name: Lockfile freshness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
enable-cache: true
cache-dependency-glob: |
Expand All @@ -34,15 +41,15 @@ jobs:
name: Ruff Linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
enable-cache: true
cache-dependency-glob: |
Expand All @@ -63,15 +70,15 @@ jobs:
name: REUSE compliance
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
enable-cache: true
cache-dependency-glob: |
Expand All @@ -88,15 +95,15 @@ jobs:
name: Mypy Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
enable-cache: true
cache-dependency-glob: |
Expand Down
19 changes: 13 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ jobs:
- name: Resolve release version from tag
id: resolve
shell: python
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
import os
import re
import sys

tag = "${{ inputs.tag }}".strip()
tag = os.environ["INPUT_TAG"].strip()
version = tag[1:] if tag.startswith("v") else tag

if not re.fullmatch(r"\d+\.\d+\.\d+", version):
Expand All @@ -77,11 +79,13 @@ jobs:
- name: Validate package path (allowlist)
id: validate
shell: python
env:
INPUT_PACKAGE_PATH: ${{ inputs.package_path }}
run: |
import os
import sys

raw = "${{ inputs.package_path }}".strip().replace("\\", "/")
raw = os.environ["INPUT_PACKAGE_PATH"].strip().replace("\\", "/")
norm = os.path.normpath(raw).replace("\\", "/")
if norm.startswith("..") or os.path.isabs(raw):
print("ERROR: invalid package_path", file=sys.stderr)
Expand Down Expand Up @@ -178,7 +182,7 @@ jobs:
ref: refs/tags/${{ needs.prerequisites.outputs.release_tag }}

- name: Set up Python
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.11"

Expand Down Expand Up @@ -266,12 +270,12 @@ jobs:
ref: refs/tags/${{ needs.prerequisites.outputs.release_tag }}

- name: Set up Python
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.11"

- name: Download all wheel artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*-${{ needs.prerequisites.outputs.release_tag }}-${{ needs.prerequisites.outputs.artifact_slug }}
path: dist-all
Expand Down Expand Up @@ -315,8 +319,11 @@ jobs:

print(f"PASS: {len(wheels)} wheel(s) match version {expected!r}")

# dist/ holds wheels for all platforms; only the linux cp311 wheel is
# installable on this runner (Python 3.11 / ubuntu). The dependency tree
# audited by pip-audit is identical across platform wheels.
- name: Install built wheels for CVE scan
run: pip install dist/*.whl
run: pip install dist/*cp311*manylinux*.whl

- name: Vulnerability scan (CVE gate — blocks publish on HIGH or higher)
run: |
Expand Down
Loading
Loading