Skip to content

Release

Goal

Create a Developer ID signed, notarized, and shareable .app and release zip from main.

Signing Prerequisites

  • Use an explicit Apple identifier that matches the app bundle ID exactly.
  • For the current direct-download release path, use Developer ID Application signing plus notarization.
  • Developer ID Application certificate creation may require the Apple Developer Account Holder.
  • Generate the certificate signing request on the Mac that will perform final signing, then create the certificate in Apple Developer and install the returned .cer on that same Mac.
  • If the Developer ID Application certificate appears as untrusted, install the Apple Developer ID - G2 intermediate certificate from the Apple PKI page.
  • Store notarization credentials on the signing Mac before the release build:
xcrun notarytool store-credentials openscribe-notary \
  --apple-id "<apple-id-email>" \
  --team-id "<TEAMID>"

First-Time Apple Setup

  1. Create the explicit App ID in Apple Developer and make sure it matches CFBundleIdentifier exactly.
  2. On the Mac that will perform the final release signing, generate the certificate signing request in Keychain Access.
  3. Have the Apple Developer Account Holder create the Developer ID Application certificate from that CSR.
  4. Install the returned .cer on the same Mac that generated the CSR so the certificate pairs with the local private key.
  5. If Keychain Access shows the certificate as untrusted or security find-identity -v -p codesigning reports 0 valid identities, install the Apple Developer ID - G2 intermediate certificate and re-check.
  6. Store notarization credentials with notarytool and confirm the profile validates before the release build.

Signing Notes

  • Scripts/sign_and_notarize_app.sh signs bundled Mach-O files inside OpenScribe.app/Contents/Resources/bin before signing the outer app bundle.
  • The hardened runtime release app must include the microphone entitlement com.apple.security.device.audio-input so macOS can present the microphone permission prompt.
  • If new bundled executables, dynamic libraries, or frameworks are added later, they must be signed with the same Developer ID identity before the app bundle is signed and submitted for notarization.

Troubleshooting

  • Symptom: security find-identity -v -p codesigning reports 0 valid identities Cause: the certificate is missing its matching private key on the signing Mac, or the Apple Developer ID - G2 intermediate certificate is not installed.
  • Symptom: codesign prints unable to build chain to self-signed root and fails with errSecInternalComponent, while security find-identity still lists the identity as valid Cause: the Apple Developer ID - G2 intermediate certificate is missing from the signing Mac, or the Developer ID Application certificate has a custom Always Trust setting. Import https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer into the login keychain, and set the certificate trust to Use System Defaults in Keychain Access. security dump-trust-settings should list no custom trust for the signing certificate. If the error remains, run signing from Terminal and allow codesign to use the private key when Keychain asks.
  • Symptom: notarization reports bundled binaries are not signed with a valid Developer ID certificate, are missing a secure timestamp, or do not have hardened runtime Cause: nested Mach-O code inside the app bundle was not signed explicitly before the outer app signature was applied.
  • Symptom: the app never appears in Microphone settings and recording is denied immediately after launch Cause: the hardened runtime app was signed without the com.apple.security.device.audio-input entitlement, so TCC refuses to prompt for microphone access.
  • Symptom: stapling fails with Record not found Cause: the notarization submission was rejected. Fetch the notarization log with xcrun notarytool log <submission-id> --keychain-profile openscribe-notary and fix the reported validation errors before retrying.

Preflight

  • Clean working tree.
  • Verification loop passes.
  • Confirm the signing identity exists:
security find-identity -v -p codesigning
  • Confirm the notarization profile is usable:
xcrun notarytool history --keychain-profile openscribe-notary

Build release app bundles

OPENSCRIBE_BUILD_ARCH=arm64 zsh Scripts/build_release_app.sh
OPENSCRIBE_BUILD_ARCH=x86_64 zsh Scripts/build_release_app.sh

Outputs:

  • dist/OpenScribe-<version>-arm64/OpenScribe.app
  • dist/OpenScribe-<version>-arm64.zip
  • dist/OpenScribe-<version>-x86_64/OpenScribe.app
  • dist/OpenScribe-<version>-x86_64.zip

Notes:

  • This step creates a local build artifact for verification and notarization input.
  • The zip from this step is not the direct-download release asset.
  • Release builds bundle an architecture-matched whisper-cli inside OpenScribe.app.
  • On Apple Silicon Macs with Rosetta installed, the x86_64 build and unit test pass can run locally under /usr/bin/arch -x86_64.
  • Local whisper models are still downloaded on demand after install.

Smoke packaged release apps

Run the smoke harness against each built .app bundle before signing and notarization. This catches bundle-layout and resource lookup regressions that do not appear under swift run.

zsh .agents/skills/ui-smoke/scripts/run.sh \
  --app dist/OpenScribe-<version>-arm64/OpenScribe.app \
  --out artifacts/ui-smoke/release-arm64

zsh .agents/skills/ui-smoke/scripts/run.sh \
  --app dist/OpenScribe-<version>-x86_64/OpenScribe.app \
  --out artifacts/ui-smoke/release-x86_64

Notes:

  • This mode launches OpenScribe.app/Contents/MacOS/OpenScribe directly while preserving the real app bundle layout.
  • Use it to verify settings, popover, menubar icons, and packaging-sensitive paths such as bundled resources.
  • On Apple Silicon with Rosetta installed, the packaged Intel app can be smoke tested locally.

Sign and notarize release apps

Direct-download releases must be signed with Developer ID Application, notarized, and stapled before upload.

zsh Scripts/sign_and_notarize_app.sh \
  dist/OpenScribe-<version>-arm64/OpenScribe.app \
  "Developer ID Application: <Name> (<TEAMID>)" \
  openscribe-notary

zsh Scripts/sign_and_notarize_app.sh \
  dist/OpenScribe-<version>-x86_64/OpenScribe.app \
  "Developer ID Application: <Name> (<TEAMID>)" \
  openscribe-notary

Outputs for each architecture:

  • dist/OpenScribe-<version>-arm64/OpenScribe-signed.zip
  • dist/OpenScribe-<version>-arm64/OpenScribe-notarized.zip
  • dist/OpenScribe-<version>-x86_64/OpenScribe-signed.zip
  • dist/OpenScribe-<version>-x86_64/OpenScribe-notarized.zip

Validation:

spctl --assess --type execute --verbose=4 dist/OpenScribe-<version>-arm64/OpenScribe.app
spctl --assess --type execute --verbose=4 dist/OpenScribe-<version>-x86_64/OpenScribe.app

Tag and publish

git tag v<version>
git push origin v<version>

Create GitHub Release from that tag and upload:

  • dist/OpenScribe-<version>-arm64.zip
  • dist/OpenScribe-<version>-x86_64.zip
  • dist/OpenScribe-latest-arm64.zip
  • dist/OpenScribe-latest-x86_64.zip

The docs landing page, install guide, and README currently depend on these stable release asset names:

  • OpenScribe-latest-arm64.zip
  • OpenScribe-latest-x86_64.zip

Before upload, copy the notarized zip to the public asset names:

cp dist/OpenScribe-<version>-arm64/OpenScribe-notarized.zip dist/OpenScribe-<version>-arm64.zip
cp dist/OpenScribe-<version>-x86_64/OpenScribe-notarized.zip dist/OpenScribe-<version>-x86_64.zip
cp dist/OpenScribe-<version>-arm64/OpenScribe-notarized.zip dist/OpenScribe-latest-arm64.zip
cp dist/OpenScribe-<version>-x86_64/OpenScribe-notarized.zip dist/OpenScribe-latest-x86_64.zip

Release notes

Write release notes deliberately. Do not use --generate-notes as the final published body.

Create a draft from the template and fill it with the user-facing changes in the release:

mkdir -p artifacts/release-notes
cp site-docs/ops/release-notes-template.md artifacts/release-notes/v<version>.md

Default structure:

  • one short opening sentence
  • Highlights
  • optional Notes

Guidelines:

  • Keep the scope to shipped behavior in that tag.
  • Mention the primary user-facing capability first.
  • Keep the body short. Two to four highlight bullets is enough.
  • Keep verification details in this runbook, not in the published release body.
  • Do not add an Assets section. GitHub already shows the uploaded assets.
  • Mention notarization for direct-download releases when the asset was replaced or reissued.
  • Avoid raw commit lists and avoid GitHub generated notes as the published body.

Homebrew tap

OpenScribe ships via the sibling tap repo at ../homebrew-tap.

After publishing the GitHub release, regenerate the tap cask from the notarized release zips:

zsh Scripts/generate_homebrew_cask.sh \
  dist/OpenScribe-<version>-arm64.zip \
  dist/OpenScribe-<version>-x86_64.zip \
  v<version> \
  ../homebrew-tap/Casks/openscribe.rb

Then verify the tap repo:

  • brew style --display-cop-names Casks/openscribe.rb
  • brew audit --strict --online --cask streichsbaer/tap/openscribe
  • Review pinned third party GitHub Action SHAs in the tap workflow and replace any pin that GitHub flags for runtime deprecation before cutting the release.

Then verify install behavior:

cd ../homebrew-tap
brew uninstall --cask openscribe || true
brew untap streichsbaer/tap || true
brew tap streichsbaer/tap
brew install --cask streichsbaer/tap/openscribe

Notes:

  • On Apple Silicon, Homebrew should install the arm64 asset.
  • When native Intel hardware is not available, validate the x86_64 release zip with Rosetta build and test coverage locally and rely on Homebrew cask structure plus checksums for the Intel path.