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
136 changes: 136 additions & 0 deletions .github/workflows/api-parity-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: Weekly API parity PR

# Repository-owned drift detection for the hand-written CLI. The workflow
# refreshes a deterministic coverage report and opens or updates one rolling PR
# when the public API and first-class CLI command surface change. It does not
# claim to regenerate hand-written command UX from OpenAPI.
#
# Prerequisite:
# REGEN_PR_TOKEN: a token with Contents and Pull requests write access to this
# repository. The organization does not allow GITHUB_TOKEN to create PRs.

on:
workflow_dispatch:
schedule:
# Monday 10:00 UTC, after the SDK regeneration job.
- cron: "0 10 * * 1"

permissions:
contents: read
issues: write
Comment thread
coderabbitai[bot] marked this conversation as resolved.

concurrency:
group: weekly-api-parity-pr
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
ref: main
persist-credentials: false

- uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: scripts/requirements-audit.txt

- name: Install pinned dependencies
run: python -m pip install --requirement scripts/requirements-audit.txt

- name: Test the audit
run: python -m unittest scripts/test_audit_api_coverage.py

- name: Refresh the deterministic parity report
run: >-
python scripts/audit_api_coverage.py
--write-markdown api-coverage-report.md
--allow-drift

- name: Open or update the parity PR
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.REGEN_PR_TOKEN }}
commit-message: "chore(cli): refresh API parity report"
title: "chore(cli): reconcile weekly API parity"
body: |
Automated comparison of the live public OpenAPI catalog with first-class CLI commands.

- `api-coverage-report.md` is deterministic and changes only when API or CLI coverage changes.
- This PR does not claim to generate the CLI's hand-written command UX.
- If the report says `ACTION REQUIRED`, implement the missing commands or explicitly review the manifest exception, regenerate the report, and get CI green before merging.

The branch is stable, so future weekly runs update this PR instead of opening duplicates.
branch: chore/weekly-api-parity
delete-branch: false
add-paths: api-coverage-report.md

- name: Open or update the automation failure issue
if: failure()
uses: actions/github-script@v7
with:
script: |
const title = 'Weekly CLI API parity PR automation is failing';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = [
'The scheduled CLI API parity workflow failed before it could produce or update its rolling PR.',
'',
`Run: ${runUrl}`,
'',
'This issue reports broken automation, not ordinary API drift. Ordinary drift is reported through the rolling PR.',
].join('\n');
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
const existing = issues.find((issue) => !issue.pull_request && issue.title === title);
if (existing) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.number,
body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
});
}

- name: Close a recovered automation failure issue
if: success()
uses: actions/github-script@v7
with:
script: |
const title = 'Weekly CLI API parity PR automation is failing';
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
const existing = issues.find((issue) => !issue.pull_request && issue.title === title);
if (existing) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.number,
body: `Recovered in ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}.`,
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.number,
state: 'closed',
state_reason: 'completed',
});
}
40 changes: 38 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,45 @@ on:
env:
CARGO_TERM_COLOR: always

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: scripts/requirements-audit.txt

- name: Install API audit dependencies
run: python -m pip install --requirement scripts/requirements-audit.txt

- name: Test automation scripts
run: python -m unittest discover --start-directory scripts --pattern 'test_*.py'

- name: Lint automation scripts
run: |
python -m ruff check scripts
python -m ruff format --check scripts

- name: Audit live API command coverage
# Live API/network failures are advisory on ordinary CI runs. The
# repository-owned weekly parity PR is the strict reconciliation gate.
continue-on-error: ${{ github.event_name != 'pull_request' || github.head_ref != 'chore/weekly-api-parity' }}
run: |
python scripts/audit_api_coverage.py \
--write-markdown api-coverage-report.md
git diff --exit-code -- api-coverage-report.md
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Validate release manifests
run: python scripts/release_version.py

- uses: dtolnay/rust-toolchain@stable
with:
Expand All @@ -25,10 +59,10 @@ jobs:
run: cargo fmt --check

- name: Clippy
run: cargo clippy -- -D warnings
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Test
run: cargo test
run: cargo test --all-targets --all-features

build:
needs: check
Expand All @@ -55,6 +89,8 @@ jobs:

steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- uses: dtolnay/rust-toolchain@stable
with:
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/release-on-version-bump.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Release on version bump

# Wait for the exact main-branch CI run to pass, then release an untagged Cargo
# version. The release workflow is called directly because a tag pushed with
# GITHUB_TOKEN does not trigger another workflow.

on:
workflow_run:
workflows: ["CI"]
types: [completed]
workflow_dispatch:

permissions:
contents: write
issues: write

concurrency:
group: release-on-version-bump
cancel-in-progress: false

jobs:
prepare:
if: >-
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
)
runs-on: ubuntu-latest
outputs:
release: ${{ steps.tag.outputs.release }}
release_ref: ${{ steps.tag.outputs.release_ref }}
tag: ${{ steps.version.outputs.tag }}
steps:
- name: Resolve the CI-tested commit
id: ref
env:
WORKFLOW_RUN_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
if [ "$GITHUB_REF_NAME" != "main" ]; then
echo "::error::Manual releases must be dispatched from main (got $GITHUB_REF_NAME)."
exit 1
fi
echo "sha=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
else
echo "sha=$WORKFLOW_RUN_SHA" >> "$GITHUB_OUTPUT"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- uses: actions/checkout@v6
with:
ref: ${{ steps.ref.outputs.sha }}
fetch-depth: 0

- uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Read and validate the release version
id: version
run: |
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
python scripts/release_version.py --github-output "$GITHUB_OUTPUT"
else
python scripts/release_version.py \
--allow-non-release \
--github-output "$GITHUB_OUTPUT"
fi

- name: Tag an unreleased version
id: tag
env:
RELEASE_CANDIDATE: ${{ steps.version.outputs.release_candidate }}
RELEASE_SHA: ${{ steps.ref.outputs.sha }}
RELEASE_TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
if [ "$RELEASE_CANDIDATE" != "true" ]; then
echo "Cargo.toml does not contain a stable release candidate; nothing to do."
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi

git fetch --tags --force

if git rev-parse -q --verify "refs/tags/$RELEASE_TAG" >/dev/null; then
existing_sha="$(git rev-list -n 1 "$RELEASE_TAG")"
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
echo "Manual recovery will rerun the existing ${RELEASE_TAG} release."
echo "release=true" >> "$GITHUB_OUTPUT"
echo "release_ref=$existing_sha" >> "$GITHUB_OUTPUT"
else
echo "${RELEASE_TAG} is already released; nothing to do."
echo "release=false" >> "$GITHUB_OUTPUT"
echo "release_ref=$existing_sha" >> "$GITHUB_OUTPUT"
fi
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "$RELEASE_TAG"
git push origin "$RELEASE_TAG"
echo "release=true" >> "$GITHUB_OUTPUT"
echo "release_ref=$RELEASE_SHA" >> "$GITHUB_OUTPUT"

release:
needs: prepare
if: needs.prepare.outputs.release == 'true'
uses: ./.github/workflows/release.yml
with:
release_ref: ${{ needs.prepare.outputs.release_ref }}
tag_name: ${{ needs.prepare.outputs.tag }}
secrets: inherit
Loading