Files
agent-pet-companion/.github/workflows/release.yml
T
xjxandgd-ops 790046d9a8 Add protected parallel delivery workflow (#6)
## Development lane / 开发通道

- Lane: `direct-to-main` bootstrap
- Base branch: `main`
- Active train: `n/a`
- Owning Agent/session: workflow coordinator

## Change / 改动

- Split authoritative CI into parallel static, contract, Rust lint/test
shards, Swift interaction, stress, overlay, bundle, and stable `Required
CI` jobs.
- Make every ready PR to `main` a complete gate while keeping task PRs
to `gd-ops/train/*` path-scoped.
- Add direct/train worktree coordination, bounded changelog fragments,
protected main/train ruleset automation, and safe same-repository squash
auto-merge.
- Let GitHub Release promote an exact successful main commit, reuse its
source proof, and create a missing release tag only after proof
validation.
- Keep local pre-push validation bounded to basic syntax, formatting,
contracts, and compilation; complete duplicate proof stays in GitHub CI.

This is the one bootstrap PR: the auto-merge workflow and required rules
do not exist on the base branch yet. After this PR passes and is merged
once, the exact `main` push must pass `Required CI`; then the
fail-closed applicator can activate both rulesets and all later ready
`gd-ops/*` PRs follow the automated path.

## Validation / 验证

- `actionlint .github/workflows/ci.yml .github/workflows/release.yml
.github/workflows/auto-merge.yml`
- `./script/validate_source_syntax.sh`
- `./script/validate_build_scripts_safety.sh --skip-source-syntax`
- Exact staged diff was reapplied to a clean detached worktree before
the final validation run.
- 28 validation-tooling tests, 40 release-distribution tests, macOS
build-contract tests, Release shell contracts, and test-isolation
validation passed.

## Risk / 风险

- No product runtime or credential access is added.
- `pull_request_target` never checks out or executes PR code and only
enables auto-merge after API verification of active PR and `Required CI`
rules.
- Full ruleset activation fails closed until the exact remote main
commit has a trusted successful GitHub Actions push run.
- Reversible repository settings are already enabled: auto-merge,
squash-only, update branch, and delete merged branches.

---------

Co-authored-by: gd-ops <gd-ops@xmfunny.com>
2026-08-11 20:28:02 +08:00

581 lines
25 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: GitHub Release for macOS
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: vX.Y.Z tag to create or reuse
required: true
type: string
build:
description: Positive CFBundleVersion
required: true
default: "1"
type: string
commit:
description: Successful main commit to promote (blank uses current main)
required: false
type: string
permissions:
contents: read
concurrency:
group: github-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
cancel-in-progress: false
jobs:
prepare:
name: Reuse exact main source proof
runs-on: macos-26
timeout-minutes: 20
permissions:
actions: read
contents: read
outputs:
tag: ${{ steps.release_identity.outputs.tag }}
version: ${{ steps.release_identity.outputs.version }}
build: ${{ steps.release_identity.outputs.build }}
commit: ${{ steps.release_identity.outputs.commit }}
previous_tag: ${{ steps.release_identity.outputs.previous_tag }}
tag_exists: ${{ steps.release_identity.outputs.tag_exists }}
env:
APC_VALIDATE_HOST_UI: "0"
APC_VALIDATE_REAL_AGENT_CONNECTORS: "0"
APC_VALIDATE_REAL_APP_SERVER: "0"
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && (inputs.commit || 'main') || github.ref }}
- name: Resolve release identity
id: release_identity
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_TAG: ${{ inputs.tag }}
DISPATCH_BUILD: ${{ inputs.build }}
PUSH_TAG: ${{ github.ref_name }}
PUSH_BUILD: ${{ github.run_number }}
run: |
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
tag="$DISPATCH_TAG"
build="$DISPATCH_BUILD"
else
tag="$PUSH_TAG"
build="$PUSH_BUILD"
fi
[[ "$tag" =~ ^v[0-9]+[.][0-9]+[.][0-9]+$ ]]
[[ "$build" =~ ^[1-9][0-9]*$ ]]
version="${tag#v}"
commit="$(git rev-parse HEAD)"
git rev-parse --verify refs/remotes/origin/main >/dev/null
git merge-base --is-ancestor "$commit" refs/remotes/origin/main
tag_exists=0
if git rev-parse --verify "refs/tags/$tag^{commit}" >/dev/null 2>&1; then
test "$(git rev-list -n 1 "$tag")" = "$commit"
tag_exists=1
elif [[ "$EVENT_NAME" != "workflow_dispatch" ]]; then
echo "tag push did not resolve to a local tag" >&2
exit 1
fi
source_version="$(awk -F'"' '/^version = / {print $2; exit}' crates/petcore/Cargo.toml)"
test "$version" = "$source_version"
grep -q "^## \\[$version\\] - [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$" CHANGELOG.md
previous_tag="$(
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
"repos/$GITHUB_REPOSITORY/releases/latest" \
--jq '.tag_name'
)"
[[ "$previous_tag" =~ ^v[0-9]+[.][0-9]+[.][0-9]+$ ]]
test "$previous_tag" != "$tag"
previous_commit="$(git rev-parse --verify "$previous_tag^{commit}")"
git merge-base --is-ancestor "$previous_commit" "$commit^"
./script/validate_codex_plugin_version.py --base-ref "$previous_tag"
{
echo "tag=$tag"
echo "version=$version"
echo "build=$build"
echo "commit=$commit"
echo "previous_tag=$previous_tag"
echo "tag_exists=$tag_exists"
} >>"$GITHUB_OUTPUT"
- name: Prepare source-proof verification host
run: |
test "$(uname -m)" = "arm64"
./script/validate_macos_build_contract.py toolchain
- name: Resolve successful trusted main CI run
id: source_proof_run
run: |
runs_json="$RUNNER_TEMP/apc-ci-runs.json"
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
"repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs?branch=main&event=push&status=success&head_sha=${{ steps.release_identity.outputs.commit }}&per_page=100" \
>"$runs_json"
selection="$(
./script/resolve_release_source_proof.py run \
--runs-json "$runs_json" \
--repository "$GITHUB_REPOSITORY" \
--commit "${{ steps.release_identity.outputs.commit }}"
)"
run_id="$(jq -r '.run_id' <<<"$selection")"
run_attempt="$(jq -r '.run_attempt' <<<"$selection")"
[[ "$run_id" =~ ^[1-9][0-9]*$ ]]
[[ "$run_attempt" =~ ^[1-9][0-9]*$ ]]
artifacts_json="$RUNNER_TEMP/apc-ci-artifacts.json"
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
"repos/$GITHUB_REPOSITORY/actions/runs/$run_id/artifacts?per_page=100" \
>"$artifacts_json"
artifact_name="release-source-proof-${{ steps.release_identity.outputs.commit }}"
./script/resolve_release_source_proof.py artifact \
--artifacts-json "$artifacts_json" \
--run-id "$run_id" \
--artifact-name "$artifact_name" >/dev/null
{
echo "run_id=$run_id"
echo "run_attempt=$run_attempt"
echo "artifact_name=$artifact_name"
} >>"$GITHUB_OUTPUT"
- name: Download exact-commit source proof from trusted CI run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ steps.source_proof_run.outputs.artifact_name }}
path: ${{ runner.temp }}/source-proof
github-token: ${{ github.token }}
repository: ${{ github.repository }}
run-id: ${{ steps.source_proof_run.outputs.run_id }}
- name: Verify exact-commit source proof and rebind interaction attestation
env:
APC_BUILD_ID: ${{ steps.release_identity.outputs.version }}.${{ steps.release_identity.outputs.build }}.${{ steps.release_identity.outputs.commit }}
run: |
./script/release_source_proof.py validate \
--repository "$GITHUB_REPOSITORY" \
--commit "${{ steps.release_identity.outputs.commit }}" \
--previous-release-tag "${{ steps.release_identity.outputs.previous_tag }}" \
--run-id "${{ steps.source_proof_run.outputs.run_id }}" \
--run-attempt "${{ steps.source_proof_run.outputs.run_attempt }}" \
--proof "$RUNNER_TEMP/source-proof/source-proof.json" \
--attestation "$RUNNER_TEMP/source-proof/interaction-attestation.json"
./script/prepare_interaction_attestation.sh \
--output "$RUNNER_TEMP/release-interaction-attestation.json" \
--proof-in "$RUNNER_TEMP/source-proof/interaction-attestation.json"
- name: Upload release-bound interaction proof
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-interaction-attestation
if-no-files-found: error
retention-days: 3
path: ${{ runner.temp }}/release-interaction-attestation.json
ensure_tag:
name: Bind release tag to proven main commit
needs: prepare
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
RELEASE_COMMIT: ${{ needs.prepare.outputs.commit }}
TAG_EXISTS: ${{ needs.prepare.outputs.tag_exists }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- name: Create missing lightweight tag after source proof succeeds
if: env.TAG_EXISTS != '1'
run: |
gh api --method POST \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
"repos/$GITHUB_REPOSITORY/git/refs" \
-f "ref=refs/tags/$RELEASE_TAG" \
-f "sha=$RELEASE_COMMIT" >/dev/null
- name: Verify immutable release identity
run: ./script/verify_remote_release_tag.sh --tag "$RELEASE_TAG" --commit "$RELEASE_COMMIT"
build_archives:
name: Build ${{ matrix.architecture }} archive
needs: [prepare, ensure_tag]
strategy:
fail-fast: false
matrix:
architecture: [arm64, x86_64]
runs-on: macos-26
timeout-minutes: 60
permissions:
contents: read
env:
APC_VALIDATE_HOST_UI: "0"
APC_VALIDATE_REAL_AGENT_CONNECTORS: "0"
APC_VALIDATE_REAL_APP_SERVER: "0"
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: release-interaction-attestation
path: ${{ runner.temp }}/release-proof
- name: Prepare architecture build host
run: |
test "$(uname -m)" = "arm64"
rustup show
rustup target add "${{ matrix.architecture == 'arm64' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}"
./script/validate_macos_build_contract.py toolchain
if ! command -v rg >/dev/null 2>&1; then
brew install ripgrep
fi
command -v codesign
command -v ditto
command -v lipo
command -v shasum
- name: Prepare pinned Python validation environment
run: |
python3 -m venv "$RUNNER_TEMP/apc-validation-python"
"$RUNNER_TEMP/apc-validation-python/bin/python" -m pip install \
--disable-pip-version-check --no-deps --only-binary=:all: "Pillow==11.3.0"
echo "$RUNNER_TEMP/apc-validation-python/bin" >>"$GITHUB_PATH"
- name: Restore source/build cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
apps/macos/.build
key: macos-release-${{ hashFiles('rust-toolchain.toml', 'Cargo.lock', 'apps/macos/Package.swift', 'apps/macos/Package.resolved') }}-${{ needs.prepare.outputs.commit }}
restore-keys: |
macos-release-${{ hashFiles('rust-toolchain.toml', 'Cargo.lock', 'apps/macos/Package.swift', 'apps/macos/Package.resolved') }}-
- name: Build one source-proven official component
env:
APC_RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
APC_RELEASE_BUILD: ${{ needs.prepare.outputs.build }}
APC_PREVIOUS_RELEASE_TAG: ${{ needs.prepare.outputs.previous_tag }}
APC_INTERACTION_ATTESTATION_PATH: ${{ runner.temp }}/release-proof/release-interaction-attestation.json
run: ./script/build_release.sh --github-release --source-gate-proven --arch "${{ matrix.architecture }}"
- name: Upload architecture component
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-component-${{ matrix.architecture }}
if-no-files-found: error
retention-days: 3
path: dist/AgentPetCompanion-${{ needs.prepare.outputs.version }}-macos-${{ matrix.architecture }}.zip
assemble:
name: Assemble exact release candidate
needs: [prepare, build_archives]
runs-on: macos-15
timeout-minutes: 15
permissions:
contents: read
outputs:
arm64_zip_sha256: ${{ steps.candidate_digests.outputs.arm64_zip_sha256 }}
x86_64_zip_sha256: ${{ steps.candidate_digests.outputs.x86_64_zip_sha256 }}
checksum_sha256: ${{ steps.candidate_digests.outputs.checksum_sha256 }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: release-component-*
path: release-assets
merge-multiple: true
- name: Create and validate exact three-file candidate
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
run: |
(
cd release-assets
shasum -a 256 \
"AgentPetCompanion-$RELEASE_VERSION-macos-arm64.zip" \
"AgentPetCompanion-$RELEASE_VERSION-macos-x86_64.zip" \
>"AgentPetCompanion-$RELEASE_VERSION-SHA256SUMS.txt"
)
./script/validate_release_artifact_metadata.py \
--directory release-assets \
--version "$RELEASE_VERSION"
- name: Emit trusted digest for every candidate file
id: candidate_digests
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
run: |
digest() { shasum -a 256 "$1" | awk '{print $1}'; }
{
echo "arm64_zip_sha256=$(digest "release-assets/AgentPetCompanion-$RELEASE_VERSION-macos-arm64.zip")"
echo "x86_64_zip_sha256=$(digest "release-assets/AgentPetCompanion-$RELEASE_VERSION-macos-x86_64.zip")"
echo "checksum_sha256=$(digest "release-assets/AgentPetCompanion-$RELEASE_VERSION-SHA256SUMS.txt")"
} >>"$GITHUB_OUTPUT"
- name: Upload exact release candidate
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: AgentPetCompanion-${{ needs.prepare.outputs.version }}-github-release
if-no-files-found: error
retention-days: 14
path: release-assets
validate_arm64:
name: Validate packaged arm64 App on macOS 15 compatibility host
needs: [prepare, assemble]
runs-on: macos-15
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Prove native validation architecture
run: test "$(uname -m)" = "arm64"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: AgentPetCompanion-${{ needs.prepare.outputs.version }}-github-release
path: release-assets
- name: Compare trusted digests and run only native arm64 packaged validation
run: |
./script/verify_release_candidate_digests.sh \
--directory release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--arm64-zip-sha256 "${{ needs.assemble.outputs.arm64_zip_sha256 }}" \
--x86_64-zip-sha256 "${{ needs.assemble.outputs.x86_64_zip_sha256 }}" \
--checksum-sha256 "${{ needs.assemble.outputs.checksum_sha256 }}"
./script/validate_github_release_artifacts.sh \
--directory release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--build "${{ needs.prepare.outputs.build }}" \
--commit "${{ needs.prepare.outputs.commit }}" \
--require-native-architecture arm64
validate_x86_64:
name: Validate packaged x86_64 App on macOS 15 Intel compatibility host
needs: [prepare, assemble]
runs-on: macos-15-intel
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Prove native validation architecture
run: test "$(uname -m)" = "x86_64"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: AgentPetCompanion-${{ needs.prepare.outputs.version }}-github-release
path: release-assets
- name: Compare trusted digests and run only native x86_64 packaged validation
run: |
./script/verify_release_candidate_digests.sh \
--directory release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--arm64-zip-sha256 "${{ needs.assemble.outputs.arm64_zip_sha256 }}" \
--x86_64-zip-sha256 "${{ needs.assemble.outputs.x86_64_zip_sha256 }}" \
--checksum-sha256 "${{ needs.assemble.outputs.checksum_sha256 }}"
./script/validate_github_release_artifacts.sh \
--directory release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--build "${{ needs.prepare.outputs.build }}" \
--commit "${{ needs.prepare.outputs.commit }}" \
--require-native-architecture x86_64
validate_macos26:
name: Validate packaged Liquid Glass path on macOS 26
needs: [prepare, assemble]
runs-on: macos-26
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Prove native macOS 26 validation host
run: |
test "$(uname -m)" = "arm64"
test "$(sw_vers -productVersion | cut -d. -f1)" -ge 26
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: AgentPetCompanion-${{ needs.prepare.outputs.version }}-github-release
path: release-assets
- name: Compare trusted digests and run packaged macOS 26 validation
run: |
./script/verify_release_candidate_digests.sh \
--directory release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--arm64-zip-sha256 "${{ needs.assemble.outputs.arm64_zip_sha256 }}" \
--x86_64-zip-sha256 "${{ needs.assemble.outputs.x86_64_zip_sha256 }}" \
--checksum-sha256 "${{ needs.assemble.outputs.checksum_sha256 }}"
./script/validate_github_release_artifacts.sh \
--directory release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--build "${{ needs.prepare.outputs.build }}" \
--commit "${{ needs.prepare.outputs.commit }}" \
--require-native-architecture arm64
publish:
name: Verify and publish GitHub Release
needs: [prepare, assemble, validate_arm64, validate_x86_64, validate_macos26]
runs-on: macos-15
timeout-minutes: 30
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
RELEASE_BUILD: ${{ needs.prepare.outputs.build }}
RELEASE_COMMIT: ${{ needs.prepare.outputs.commit }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
ref: ${{ needs.prepare.outputs.commit }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: AgentPetCompanion-${{ needs.prepare.outputs.version }}-github-release
path: release-assets
- name: Compare exact inventory and trusted build digests
run: |
./script/verify_release_candidate_digests.sh \
--directory release-assets \
--version "$RELEASE_VERSION" \
--arm64-zip-sha256 "${{ needs.assemble.outputs.arm64_zip_sha256 }}" \
--x86_64-zip-sha256 "${{ needs.assemble.outputs.x86_64_zip_sha256 }}" \
--checksum-sha256 "${{ needs.assemble.outputs.checksum_sha256 }}"
- name: Prove remote release tag still targets the candidate
run: ./script/verify_remote_release_tag.sh --tag "$RELEASE_TAG" --commit "$RELEASE_COMMIT"
- name: Create draft, verify downloaded bytes, and publish latest stable Release
run: |
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
echo "GitHub Release already exists for $RELEASE_TAG; refusing replacement" >&2
exit 1
fi
notes_file="$RUNNER_TEMP/apc-release-notes.md"
{
printf '# Agent Pet Companion %s\n\n' "$RELEASE_VERSION"
printf '## Update in three steps / 三步更新\n\n'
printf '1. Download and unzip the archive for this Mac.\n'
printf '2. Quit Agent Pet Companion, move the new App to Applications, and choose Replace.\n'
printf '3. Open the new App from Applications.\n\n'
printf '1. 下载并解压适用于这台 Mac 的归档。\n'
printf '2. 退出 Agent Pet Companion,将新版移入“应用程序”,并选择“替换”。\n'
printf '3. 从“应用程序”打开新版。\n\n'
printf 'Your pets, settings, history, and active work stay on this Mac and are preserved.\n\n'
printf '你的宠物、设置、历史和正在进行的工作会留在这台 Mac 上并保持不变。\n\n'
# shellcheck disable=SC2016
printf 'Apple silicon uses `macos-arm64`; Intel uses `macos-x86_64`. Download `SHA256SUMS.txt` with the ZIP and verify the selected archive before replacing the App.\n\n'
# shellcheck disable=SC2016
printf 'Apple 芯片使用 `macos-arm64`,Intel 使用 `macos-x86_64`。请同时下载 `SHA256SUMS.txt`,并在替换 App 前核对所选归档。\n\n'
printf '**First launch:** these Apps are ad-hoc signed, are not Developer ID signed, and are not Apple-notarized. If macOS blocks the App, Control-click it in Finder and choose **Open**, or use **System Settings → Privacy & Security → Open Anyway**. Only continue if you downloaded it from this GitHub Release and its SHA-256 matches the published checksum.\n\n'
printf '**首次打开:** 当前 App 使用 ad-hoc 签名,没有 Developer ID 签名,也未经过 Apple 公证。如果 macOS 阻止打开,请在 Finder 中按住 Control 点按 App 并选择“打开”,或前往“系统设置 → 隐私与安全性 → 仍要打开”。仅在文件来自本 GitHub Release 且 SHA-256 与校验和一致时继续。\n'
} >"$notes_file"
assets=(
"release-assets/AgentPetCompanion-$RELEASE_VERSION-macos-arm64.zip"
"release-assets/AgentPetCompanion-$RELEASE_VERSION-macos-x86_64.zip"
"release-assets/AgentPetCompanion-$RELEASE_VERSION-SHA256SUMS.txt"
)
gh release create "$RELEASE_TAG" "${assets[@]}" \
--verify-tag --draft \
--title "Agent Pet Companion $RELEASE_VERSION" \
--notes-file "$notes_file"
release_created=1
release_published=0
cleanup_draft() {
if [[ "$release_created" == "1" && "$release_published" == "0" ]]; then
gh release delete "$RELEASE_TAG" --yes >/dev/null 2>&1 || true
fi
}
trap cleanup_draft EXIT
download_dir="$RUNNER_TEMP/apc-release-download"
rm -rf "$download_dir"
mkdir -p "$download_dir"
gh release download "$RELEASE_TAG" --dir "$download_dir"
./script/verify_release_candidate_digests.sh \
--directory "$download_dir" \
--version "$RELEASE_VERSION" \
--arm64-zip-sha256 "${{ needs.assemble.outputs.arm64_zip_sha256 }}" \
--x86_64-zip-sha256 "${{ needs.assemble.outputs.x86_64_zip_sha256 }}" \
--checksum-sha256 "${{ needs.assemble.outputs.checksum_sha256 }}"
./script/verify_remote_release_tag.sh --tag "$RELEASE_TAG" --commit "$RELEASE_COMMIT"
gh release edit "$RELEASE_TAG" --draft=false --latest
release_published=1
./script/verify_remote_release_tag.sh --tag "$RELEASE_TAG" --commit "$RELEASE_COMMIT"
published_json="$RUNNER_TEMP/apc-published-release.json"
latest_json="$RUNNER_TEMP/apc-latest-release.json"
gh api -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2026-03-10' \
"repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" >"$published_json"
gh api -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2026-03-10' \
"repos/$GITHUB_REPOSITORY/releases/latest" >"$latest_json"
./script/validate_github_release_api.py \
--release-json "$published_json" \
--latest-json "$latest_json" \
--repository "$GITHUB_REPOSITORY" \
--version "$RELEASE_VERSION" \
--arm64-zip-sha256 "${{ needs.assemble.outputs.arm64_zip_sha256 }}" \
--x86-64-zip-sha256 "${{ needs.assemble.outputs.x86_64_zip_sha256 }}" \
--checksum-sha256 "${{ needs.assemble.outputs.checksum_sha256 }}"
release_created=0