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 Applicationsigning plus notarization. Developer ID Applicationcertificate creation may require the Apple DeveloperAccount 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
.ceron that same Mac. - If the
Developer ID Applicationcertificate appears as untrusted, install the AppleDeveloper ID - G2intermediate 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¶
- Create the explicit App ID in Apple Developer and make sure it matches
CFBundleIdentifierexactly. - On the Mac that will perform the final release signing, generate the certificate signing request in Keychain Access.
- Have the Apple Developer
Account Holdercreate theDeveloper ID Applicationcertificate from that CSR. - Install the returned
.ceron the same Mac that generated the CSR so the certificate pairs with the local private key. - If Keychain Access shows the certificate as untrusted or
security find-identity -v -p codesigningreports0 valid identities, install the AppleDeveloper ID - G2intermediate certificate and re-check. - Store notarization credentials with
notarytooland confirm the profile validates before the release build.
Signing Notes¶
Scripts/sign_and_notarize_app.shsigns bundled Mach-O files insideOpenScribe.app/Contents/Resources/binbefore signing the outer app bundle.- The hardened runtime release app must include the microphone entitlement
com.apple.security.device.audio-inputso 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 IDidentity before the app bundle is signed and submitted for notarization.
Troubleshooting¶
- Symptom:
security find-identity -v -p codesigningreports0 valid identitiesCause: the certificate is missing its matching private key on the signing Mac, or the AppleDeveloper ID - G2intermediate certificate is not installed. - Symptom:
codesignprintsunable to build chain to self-signed rootand fails witherrSecInternalComponent, whilesecurity find-identitystill lists the identity as valid Cause: the AppleDeveloper ID - G2intermediate certificate is missing from the signing Mac, or theDeveloper ID Applicationcertificate has a customAlways Trustsetting. Importhttps://www.apple.com/certificateauthority/DeveloperIDG2CA.cerinto the login keychain, and set the certificate trust toUse System Defaultsin Keychain Access.security dump-trust-settingsshould list no custom trust for the signing certificate. If the error remains, run signing from Terminal and allowcodesignto use the private key when Keychain asks. - Symptom: notarization reports bundled binaries are not signed with a valid
Developer IDcertificate, 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-inputentitlement, so TCC refuses to prompt for microphone access. - Symptom: stapling fails with
Record not foundCause: the notarization submission was rejected. Fetch the notarization log withxcrun notarytool log <submission-id> --keychain-profile openscribe-notaryand 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.appdist/OpenScribe-<version>-arm64.zipdist/OpenScribe-<version>-x86_64/OpenScribe.appdist/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-cliinsideOpenScribe.app. - On Apple Silicon Macs with Rosetta installed, the
x86_64build 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/OpenScribedirectly 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.zipdist/OpenScribe-<version>-arm64/OpenScribe-notarized.zipdist/OpenScribe-<version>-x86_64/OpenScribe-signed.zipdist/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.zipdist/OpenScribe-<version>-x86_64.zipdist/OpenScribe-latest-arm64.zipdist/OpenScribe-latest-x86_64.zip
The docs landing page, install guide, and README currently depend on these stable release asset names:
OpenScribe-latest-arm64.zipOpenScribe-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
Assetssection. 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.rbbrew 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
arm64asset. - When native Intel hardware is not available, validate the
x86_64release zip with Rosetta build and test coverage locally and rely on Homebrew cask structure plus checksums for the Intel path.
Related¶
- Validation loop: Testing