phase8: Developer ID signing + notarization — make release, entitlements, notarize.sh; ALL PHASES COMPLETE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 3 changed files with +97 and −1
modified
Resources/ZyquoRouter.entitlements
+7 −0
@@ -2,6 +2,13 @@ | ||
| 2 | 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 3 | 3 | <plist version="1.0"> |
| 4 | 4 | <dict> |
| 5 | + <!-- Zyquo Router is a developer tool that runs a local HTTP gateway and | |
| 6 | + calls upstream AI providers with the user's own keys. It is NOT | |
| 7 | + sandboxed (like the rest of the Zyquo family): a non-sandboxed | |
| 8 | + Developer ID app needs no entitlement to listen on localhost or to | |
| 9 | + make outbound connections — com.apple.security.network.server/.client | |
| 10 | + only apply under the App Sandbox. Hardened Runtime is enabled at | |
| 11 | + signing time; no runtime exceptions are required. --> | |
| 5 | 12 | <key>com.apple.security.cs.allow-jit</key> |
| 6 | 13 | <false/> |
| 7 | 14 | </dict> |
modified
docs/PLAN.md
+26 −1
@@ -248,4 +248,29 @@ log bodies redacted by default. 26 tests green; zero warnings; headers swept. | ||
| 248 | 248 | highlights table, build targets, docs index) |
| 249 | 249 | - Light theme remains the flagship; all tokens unchanged except radii. |
| 250 | 250 | |
| 251 | −## Phase 8 — Signing & notarization — pending | |
| 251 | +## Phase 8 — Signing & notarization | |
| 252 | + | |
| 253 | +- [x] 8.1 Inspect `~/Desktop/other/OTHER/zyquo-term`: Developer ID identity, Team ID, notarytool profile, entitlements — reuse exactly (never print/commit secrets) | |
| 254 | +- [x] 8.2 `entitlements.plist` with Hardened Runtime, minimal set; NOT sandboxed (developer tool serving localhost) — documented | |
| 255 | +- [x] 8.3 `make release`: universal (arm64+x86_64 lipo) → assemble → codesign `--options runtime --timestamp` → ditto zip → `notarytool submit --wait` → staple | |
| 256 | +- [x] 8.4 `spctl -a -vv` says "accepted, source=Notarized Developer ID"; `stapler validate` passes | |
| 257 | +- [x] 8.5 Optional signed DMG | |
| 258 | +- [x] 8.6 `make dev` (ad-hoc) still works for iteration | |
| 259 | + | |
| 260 | +**Phase gate: PASSED (2026-07-31).** | |
| 261 | + | |
| 262 | +**Phase 8 summary:** Reused the zyquo-term pipeline exactly: identity `Developer ID | |
| 263 | +Application: Simon-Pierre Boucher (3YM54G49SN)`, notarytool keychain profile | |
| 264 | +`MacLustr-Notarize`, and the same minimal non-sandboxed entitlements (Hardened Runtime, | |
| 265 | +`allow-jit` false; rationale documented in ZyquoRouter.entitlements — localhost | |
| 266 | +listening needs no entitlement outside the sandbox). `make release`: universal binary | |
| 267 | +(arm64 + x86_64 via lipo), assemble, `codesign --force --options runtime --timestamp` | |
| 268 | +(nested executable first), `ditto -c -k` → `notarytool submit --wait` (**Accepted**), | |
| 269 | +`stapler staple`. Verified: `spctl -a -vv` = "accepted, source=Notarized Developer ID"; | |
| 270 | +`stapler validate` passes. Bonus: `dist/ZyquoRouter.dmg` built, signed, notarized | |
| 271 | +(**Accepted**) and stapled. `make dev` (ad-hoc) still works for iteration. | |
| 272 | + | |
| 273 | +--- | |
| 274 | + | |
| 275 | +**ALL PHASES COMPLETE — Definition of Done satisfied (2026-07-31).** | |
| 276 | + | |
added
scripts/notarize.sh
+64 −0
@@ -0,0 +1,64 @@ | ||
| 1 | +#!/bin/bash | |
| 2 | +# | |
| 3 | +# notarize.sh | |
| 4 | +# Zyquo Router | |
| 5 | +# | |
| 6 | +# Author: Simon-Pierre Boucher | |
| 7 | +# Mail: contact@spboucher.ai | |
| 8 | +# | |
| 9 | +# Developer ID signing + notarization + stapling, reusing the identity and | |
| 10 | +# notarytool keychain profile from the zyquo-term pipeline. Called by | |
| 11 | +# `make release` as: notarize.sh <app-dir> <identity> <profile> <entitlements> | |
| 12 | +# | |
| 13 | +# Flow: codesign (hardened runtime, nested binary first) → ditto zip → | |
| 14 | +# notarytool submit --wait → staple app → spctl/stapler verify → signed, | |
| 15 | +# notarized, stapled DMG. | |
| 16 | +# | |
| 17 | +set -euo pipefail | |
| 18 | + | |
| 19 | +APP_DIR="$1"; IDENTITY="$2"; PROFILE="$3"; ENTITLEMENTS="$4" | |
| 20 | + | |
| 21 | +APP_NAME="$(basename "$APP_DIR" .app)" | |
| 22 | +DIST="$(dirname "$APP_DIR")" | |
| 23 | +ZIP="$DIST/$APP_NAME.zip" | |
| 24 | +DMG="$DIST/ZyquoRouter.dmg" | |
| 25 | +EXEC="$APP_DIR/Contents/MacOS/ZyquoRouter" | |
| 26 | + | |
| 27 | +echo "=== Signing (Developer ID, hardened runtime) ===" | |
| 28 | +# Nested code first, then the bundle. | |
| 29 | +codesign --force --options runtime --timestamp \ | |
| 30 | + --entitlements "$ENTITLEMENTS" \ | |
| 31 | + --sign "$IDENTITY" "$EXEC" | |
| 32 | +codesign --force --options runtime --timestamp \ | |
| 33 | + --entitlements "$ENTITLEMENTS" \ | |
| 34 | + --sign "$IDENTITY" "$APP_DIR" | |
| 35 | +codesign --verify --deep --strict --verbose=2 "$APP_DIR" | |
| 36 | +echo "Signature valid." | |
| 37 | + | |
| 38 | +echo "=== Notarizing the app (profile: $PROFILE) ===" | |
| 39 | +rm -f "$ZIP" | |
| 40 | +ditto -c -k --keepParent "$APP_DIR" "$ZIP" | |
| 41 | +xcrun notarytool submit "$ZIP" --keychain-profile "$PROFILE" --wait | |
| 42 | +xcrun stapler staple "$APP_DIR" | |
| 43 | +rm -f "$ZIP" | |
| 44 | + | |
| 45 | +echo "=== Verifying ===" | |
| 46 | +spctl -a -vv "$APP_DIR" | |
| 47 | +xcrun stapler validate "$APP_DIR" | |
| 48 | + | |
| 49 | +echo "=== Building signed + notarized DMG ===" | |
| 50 | +rm -f "$DMG" | |
| 51 | +DMG_TEMP="$DIST/dmg_temp" | |
| 52 | +rm -rf "$DMG_TEMP" | |
| 53 | +mkdir -p "$DMG_TEMP" | |
| 54 | +cp -R "$APP_DIR" "$DMG_TEMP/" | |
| 55 | +ln -s /Applications "$DMG_TEMP/Applications" | |
| 56 | +hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_TEMP" -ov -format UDZO "$DMG" | |
| 57 | +rm -rf "$DMG_TEMP" | |
| 58 | +codesign --force --sign "$IDENTITY" --timestamp "$DMG" | |
| 59 | +xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait | |
| 60 | +xcrun stapler staple "$DMG" | |
| 61 | + | |
| 62 | +echo "=== Done ===" | |
| 63 | +echo "App: $APP_DIR (notarized + stapled)" | |
| 64 | +echo "DMG: $DMG (signed + notarized + stapled)" | |
| 65 | ||