Bundle signed voice resources in macOS releases (#43983)

## What changed

- Build, sign, and notarize `codex-voice-host` and its native runtime for Apple Silicon and Intel macOS release packages, granting the helper audio-input access.
- Include voice resources in primary package archives and DMGs, with a root-level `codex` symlink to `bin/codex` so the runtime can be located.
- Seal runtime receipts with post-signing hashes, require matching release versions and source builds, and bundle dependency notices, licenses, and source metadata.
- Keep voice resources out of Python wheels to preserve their older macOS compatibility; the native voice build targets macOS 14.

## Testing

Add packaging tests for alpha, beta, and stable versions, signed-byte preservation, license hashes, receipt validation, tamper detection, and exclusion of unlisted files. Extend release verification to check voice architectures, signatures, build identity, package hashes, and DMG contents.

GitOrigin-RevId: 9f9415a8b2532d655a9a8740bcdf64066ddb7472
This commit is contained in:
Benjamin Carlsson
2026-09-09 02:20:06 +00:00
committed by copyberry
parent dafb6781ee
commit 721f46a07a
15 changed files with 1734 additions and 41 deletions
+249 -33
View File
@@ -480,8 +480,61 @@ jobs:
path: |
codex-rs/dist/${{ matrix.target }}/*
build-macos-voice:
needs: tag-check
name: Build voice runtime - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-15
prefix: macos_aarch64
- target: x86_64-apple-darwin
runner: macos-15-intel
prefix: macos_x86_64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
bazelisk-version: 1.28.1
- name: Build matching helper and native runtime
shell: bash
env:
TARGET: ${{ matrix.target }}
PREFIX: ${{ matrix.prefix }}
run: |
set -euo pipefail
bazel build //codex-rs/voice-host:codex-voice-host //third_party/voice:native_runtime
runtime="bazel-bin/third_party/voice/native_runtime_${PREFIX}"
PYTHONPATH=third_party/voice python3 - "$runtime" "$TARGET" <<'PY'
from pathlib import Path
import sys
from package_runtime import runtime_files
runtime_files(Path(sys.argv[1]), sys.argv[2])
PY
mkdir -p "voice-unsigned/${TARGET}"
cp -R "$runtime" "voice-unsigned/${TARGET}/runtime"
cp bazel-bin/codex-rs/voice-host/codex-voice-host "voice-unsigned/${TARGET}/codex-voice-host"
tar -C "voice-unsigned/${TARGET}" -czf "voice-unsigned-${TARGET}.tar.gz" runtime codex-voice-host
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: voice-${{ matrix.target }}-unsigned
path: voice-unsigned-${{ matrix.target }}.tar.gz
if-no-files-found: error
sign-macos-binaries:
needs: build
needs: [build, build-macos-voice]
if: ${{ always() && needs.build.result == 'success' && needs.build-macos-voice.result == 'success' }}
name: Sign macOS binaries - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: ubuntu-latest
timeout-minutes: 45
@@ -524,6 +577,13 @@ jobs:
name: ${{ matrix.artifact_name }}-unsigned
path: ${{ runner.temp }}/unsigned-macos
- name: Download unsigned voice runtime
if: ${{ matrix.bundle == 'primary' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: voice-${{ matrix.target }}-unsigned
path: ${{ runner.temp }}/unsigned-voice
- name: Set up AKV PKCS11 macOS signing
uses: ./.github/actions/setup-akv-pkcs11-codesigning
with:
@@ -647,6 +707,57 @@ jobs:
--report-dir "$report_dir"
done
- name: Sign and notarize voice runtime
if: ${{ matrix.bundle == 'primary' }}
shell: bash
env:
TARGET: ${{ matrix.target }}
APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
run: |
set -euo pipefail
unsigned="${RUNNER_TEMP}/unsigned-voice/extracted"
signed="${GITHUB_WORKSPACE}/signed-voice/${TARGET}"
mkdir -p "$unsigned" "$signed"
tar -xzf "${RUNNER_TEMP}/unsigned-voice/voice-unsigned-${TARGET}.tar.gz" -C "$unsigned"
python3 third_party/voice/release_runtime.py stage \
--target "$TARGET" --source "$unsigned/runtime" --output "$signed/runtime"
cp "$unsigned/codex-voice-host" "$signed/codex-voice-host"
chmod 0755 "$signed/codex-voice-host"
while IFS= read -r -d '' library; do
name="$(basename "$library")"
.github/scripts/macos-signing/sign_macos_code.sh \
--target "$library" --identity unused --deep false \
--identifier "com.openai.codex.voice.${name}" \
--options runtime --timestamp true
done < <(find "$signed/runtime" -name '*.dylib' -type f -print0)
.github/scripts/macos-signing/sign_macos_code.sh \
--target "$signed/codex-voice-host" --identity unused --deep false \
--identifier com.openai.codex.voice-host --options runtime --timestamp true \
--entitlements .github/scripts/macos-signing/codex-voice-host.entitlements.plist
# Notarize the actual signed closure together, then hash those bytes.
(cd "$signed" && zip -qr "${RUNNER_TEMP}/voice-${TARGET}.zip" runtime codex-voice-host)
report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/voice"
mkdir -p "$report_dir"
python3 .github/scripts/macos-signing/notarize_with_akv.py \
--file "${RUNNER_TEMP}/voice-${TARGET}.zip" \
--report-log "${report_dir}/notarization.json" \
--max-wait-seconds 600
python3 third_party/voice/release_runtime.py seal \
--target "$TARGET" --output "$signed/runtime"
tar -C "$signed" -czf "${GITHUB_WORKSPACE}/signed-voice-${TARGET}.tar.gz" \
runtime codex-voice-host
- name: Upload signed voice runtime
if: ${{ matrix.bundle == 'primary' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: voice-${{ matrix.target }}-signed
path: signed-voice-${{ matrix.target }}.tar.gz
if-no-files-found: error
- name: Upload signed macOS binaries
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
@@ -723,6 +834,41 @@ jobs:
name: ${{ matrix.target }}-signed-resources
path: codex-rs/signed-resources/${{ matrix.target }}
- name: Download signed voice runtime
if: ${{ matrix.bundle == 'primary' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: voice-${{ matrix.target }}-signed
path: ${{ runner.temp }}/signed-voice
- name: Verify signed voice runtime
if: ${{ matrix.bundle == 'primary' }}
shell: bash
env:
TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
root="${RUNNER_TEMP}/signed-voice/${TARGET}"
mkdir -p "$root"
tar -xzf "${RUNNER_TEMP}/signed-voice/signed-voice-${TARGET}.tar.gz" -C "$root"
case "$TARGET" in
aarch64-apple-darwin) arch=arm64 ;;
x86_64-apple-darwin) arch=x86_64 ;;
*) exit 1 ;;
esac
PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$root/runtime" "$TARGET" <<'PY'
from pathlib import Path
import sys
from package_runtime import runtime_files
runtime_files(Path(sys.argv[1]), sys.argv[2], public_release=True)
PY
while IFS= read -r -d '' binary; do
lipo "$binary" -verify_arch "$arch"
codesign --verify --strict --verbose=2 "$binary"
done < <(find "$root/runtime" -name '*.dylib' -type f -print0)
lipo "$root/codex-voice-host" -verify_arch "$arch"
codesign --verify --strict --verbose=2 "$root/codex-voice-host"
- name: Verify signed macOS binaries
shell: bash
run: |
@@ -739,6 +885,43 @@ jobs:
codesign --verify --strict --verbose=2 "$resource_path"
done
- name: Stage macOS artifacts
shell: bash
run: |
set -euo pipefail
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
for binary in ${{ matrix.binaries }}; do
# Both variants package the host, but only the primary bundle publishes
# standalone binary archives to avoid duplicate release asset names.
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
continue
fi
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
done
- name: Build Codex package archive
shell: bash
env:
TARGET: ${{ matrix.target }}
BUNDLE: ${{ matrix.bundle }}
run: |
set -euo pipefail
voice_args=()
if [[ "$BUNDLE" == "primary" ]]; then
voice_args+=(--voice-signed-dir "${RUNNER_TEMP}/signed-voice/${TARGET}")
voice_args+=(--release-version "${GITHUB_REF_NAME#rust-v}")
fi
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
--target "$TARGET" \
--bundle "$BUNDLE" \
--entrypoint-dir "target/${TARGET}/release" \
--archive-dir "dist/${TARGET}" \
--rg-bin "signed-resources/${TARGET}/rg" \
--zsh-bin "signed-resources/${TARGET}/zsh" \
"${voice_args[@]}"
- name: Build unsigned macOS DMG
if: ${{ matrix.build_dmg == 'true' }}
shell: bash
@@ -763,6 +946,15 @@ jobs:
ditto "$binary_path" "${dmg_root}/${binary}"
done
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
# Keep the root-level invocation, but resolve it into the canonical
# package layout so InstallContext finds its adjacent voice runtime.
package="${RUNNER_TEMP}/codex-package-voice-${target}"
ditto "$package" "$dmg_root"
rm "${dmg_root}/codex"
ln -s bin/codex "${dmg_root}/codex"
fi
rm -f "$dmg_path"
hdiutil create \
-volname "$volname" \
@@ -784,37 +976,6 @@ jobs:
path: codex-rs/target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg
if-no-files-found: error
- name: Stage macOS artifacts
shell: bash
run: |
set -euo pipefail
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
for binary in ${{ matrix.binaries }}; do
# Both variants package the host, but only the primary bundle publishes
# standalone binary archives to avoid duplicate release asset names.
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
continue
fi
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
done
- name: Build Codex package archive
shell: bash
env:
TARGET: ${{ matrix.target }}
BUNDLE: ${{ matrix.bundle }}
run: |
set -euo pipefail
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
--target "$TARGET" \
--bundle "$BUNDLE" \
--entrypoint-dir "target/${TARGET}/release" \
--archive-dir "dist/${TARGET}" \
--rg-bin "signed-resources/${TARGET}/rg" \
--zsh-bin "signed-resources/${TARGET}/zsh"
- name: Build Python runtime wheel
if: ${{ matrix.bundle == 'primary' }}
shell: bash
@@ -837,13 +998,32 @@ jobs:
python3 -m venv "${RUNNER_TEMP}/python-runtime-build-venv"
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m pip install build
# The wheel keeps its macOS 10.9/11 tags. The native voice build
# currently targets macOS 14, so keep it in the release archives and
# DMG but leave this older-OS-compatible wheel unchanged.
wheel_archives="${RUNNER_TEMP}/voice-free-wheel/${{ matrix.target }}"
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
--target "${{ matrix.target }}" \
--bundle primary \
--entrypoint-dir "target/${{ matrix.target }}/release" \
--archive-dir "$wheel_archives" \
--rg-bin "signed-resources/${{ matrix.target }}/rg" \
--zsh-bin "signed-resources/${{ matrix.target }}/zsh"
wheel_archive="${wheel_archives}/codex-package-${{ matrix.target }}.tar.gz"
python3 - "$wheel_archive" <<'PY'
import sys
import tarfile
with tarfile.open(sys.argv[1]) as archive:
assert not any("codex-resources/voice/" in item.name for item in archive)
PY
stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
python3 \
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
stage-runtime \
"$stage_dir" \
"dist/${{ matrix.target }}/codex-package-${{ matrix.target }}.tar.gz" \
"$wheel_archive" \
--codex-version "${GITHUB_REF_NAME}" \
--platform-tag "$platform_tag"
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
@@ -1135,6 +1315,33 @@ jobs:
rm -f "$entitlements"
done
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
voice="${package_dir}/codex-resources/voice"
[[ -f "${voice}/lib/libgstreamer-1.0.0.dylib" ]]
export VOICE_BUILD_COMMIT="$(git rev-parse HEAD)"
PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$voice" "$target" "$package_dir" <<'PY'
import json
import sys
from pathlib import Path
from package_runtime import runtime_files
from runtime import digest
voice, target, package = map(Path, sys.argv[1:])
runtime_files(voice, str(target), public_release=True)
manifest = json.loads((voice / "manifest.json").read_text())
assert manifest["buildCommit"] == __import__("os").environ["VOICE_BUILD_COMMIT"]
for relative, expected in manifest["sha256"].items():
assert digest(package / relative) == expected, relative
PY
helper="${voice}/bin/codex-voice-host"
[[ "$("$helper" --build-commit)" == "$VOICE_BUILD_COMMIT" ]]
while IFS= read -r -d '' library; do
lipo "$library" -verify_arch "$expected_arch"
codesign --verify --strict --verbose=2 "$library"
done < <(find "$voice" -name '*.dylib' -type f -print0)
verify_signed_binary "$helper" "codex-voice-host"
fi
if [[ "${{ matrix.verify_dmg }}" != "true" ]]; then
exit 0
fi
@@ -1162,6 +1369,15 @@ jobs:
verify_signed_binary "${mount_dir}/${binary}" "$binary"
done
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
[[ "$(readlink "${mount_dir}/codex")" == "bin/codex" ]]
cmp "${mount_dir}/codex-package.json" "${package_dir}/codex-package.json"
cmp "${mount_dir}/codex-resources/voice/manifest.json" \
"${package_dir}/codex-resources/voice/manifest.json"
cmp "${mount_dir}/codex-resources/voice/bin/codex-voice-host" \
"${package_dir}/codex-resources/voice/bin/codex-voice-host"
fi
cleanup_mount
trap - EXIT
cp "$dmg_path" "dist/${target}/codex-${target}.dmg"