phase8: Developer ID signing + notarization — entitlements, notarize.sh, notarized+stapled universal app (spctl: Notarized Developer ID)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 3 changed files with +140 and −1
added
Resources/ZyquoAgent.entitlements
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| 3 | +<plist version="1.0"> | |
| 4 | +<dict> | |
| 5 | + <key>com.apple.security.automation.apple-events</key> | |
| 6 | + <true/> | |
| 7 | +</dict> | |
| 8 | +</plist> | |
modified
docs/PLAN.md
+26 −1
@@ -58,7 +58,32 @@ | ||
| 58 | 58 | |
| 59 | 59 | **Phase 7 checkpoint (2026-07-30):** 77/80 models green on tool calling (3 external failures documented); 11/11 scenarios and 8/8 safety tests pass. Two real bugs found and fixed by live testing: (1) AgentLoop recorded `completed` instead of `cancelled` when interrupted mid-stream; (2) harness sentinel `sleep 60` collided with an unrelated system daemon, making the cancellation leak-check unfalsifiable. Self-checks after all changes: build 0 warnings, policy 38/38, mock 0, ui-smoke pass. |
| 60 | 60 | |
| 61 | −## Phase 8 — Signing & notarization (reuse zyquo-term identity) | |
| 61 | +## Phase 8 — Signing & notarization (reuse zyquo-term identity) — ✅ COMPLETE | |
| 62 | +- [x] `Resources/ZyquoAgent.entitlements`: Hardened Runtime ON, App Sandbox deliberately OFF (the app must spawn /bin/bash + /usr/bin/osascript), single entitlement `com.apple.security.automation.apple-events` paired with `NSAppleEventsUsageDescription`. NOTE: the file must stay pure ASCII with no XML comments — codesign's AMFI parser rejects both (hit live: "AMFIUnserializeXML: syntax error"). | |
| 63 | +- [x] `scripts/notarize.sh`: signs nested code inside-out, then the executable, then the bundle (`--options runtime --timestamp --entitlements`), verifies `--deep --strict`, ditto-zips, submits to notarytool `--wait`, staples, and **fails loudly unless spctl reports "Notarized Developer ID"**; optional signed+stapled DMG via a `dmg` argument. | |
| 64 | +- [x] `make release` green end-to-end: universal binary (x86_64 + arm64 via lipo) → Info.plist → sign → notarize (submission ffe1330f-6832-46e3-9cba-ff6257d4e839, status **Accepted**) → staple → verify. | |
| 65 | +- [x] Independent verification: `spctl -a -vv` = **accepted, source=Notarized Developer ID**, `stapler validate` OK, CodeDirectory `flags=0x10000(runtime)`, TeamIdentifier 3YM54G49SN, entitlements as intended, `lipo -archs` = x86_64 arm64, AppIcon.icns + menu-bar PNGs in the bundle. | |
| 66 | +- [x] The shipped bundle's own binary passes `--verify-policy` 38/38 and `--run-mock`, and the app launches and renders the command center. | |
| 67 | + | |
| 68 | +**Phase 8 checkpoint (2026-07-30):** Notarized, stapled, Gatekeeper-accepted universal `Zyquo Agent.app` built entirely without the Xcode IDE. Final sweep: every code file carries the mandatory header, no secrets in any commit (`.secrets/` never tracked), dist artifacts gitignored. | |
| 69 | + | |
| 70 | +--- | |
| 71 | + | |
| 72 | +## Definition of Done — status | |
| 73 | +- ✅ `make release` → Developer ID-signed, notarized, stapled app (spctl-verified), no Xcode IDE | |
| 74 | +- ✅ Full plan→act→observe→reflect loop on the same providers/models as Zyquo Cloud; 77/80 agent-capable models green on native tool calling (docs/VERIFICATION.md) | |
| 75 | +- ✅ bash + AppleScript + workspace file tools with live streaming; every action through the policy gate; destructive/elevated always require approval; complete audit log | |
| 76 | +- ✅ Memory compaction keeps long tasks in-window (proven live: 4 compactions, task still correct); workspaces isolate and track task files | |
| 77 | +- ✅ Violet SVG icon → icns + menu-bar template, sibling of Cloud/Local | |
| 78 | +- ✅ Violet light theme per spec + derived dark theme, quality gate passed | |
| 79 | +- ✅ Naming coherent (`Zyquo Agent` / `com.zyquo.agent` / `ZyquoAgent`), keys in the reused AES-256-GCM vault (no Keychain) | |
| 80 | +- ✅ Mandatory header on every code file (repo-wide sweep) | |
| 81 | +- ✅ docs/: PLAN, AGENT-RESEARCH, PROVIDER-REUSE, VERIFICATION, EVALUATION | |
| 82 | + | |
| 83 | +### Known gaps (documented, not hidden) | |
| 84 | +- `requireApprovalForAppleScript` and `workspaceEscapePolicy=deny` are persisted and surfaced in Settings but not yet enforced by dedicated engine hooks (the engine already asks for AppleScript in Manual/Guarded and always asks on workspace escape) — see `AgentSettingsStore` header. | |
| 85 | +- `swift test` cannot run on a CommandLineTools-only toolchain (no XCTest module); the XCTest files exist for CI/Xcode, and `--verify-policy` / `--run-mock` / `--run-ui-smoke` are the executable equivalents. | |
| 86 | +- 3 of 80 models fail tool calling for external reasons: DeepInfra's two Gemini re-hosts strip Google's required `thought_signature`, and `kimi-k3` is capacity-throttled upstream. | |
| 62 | 87 | |
| 63 | 88 | **Facts gathered from `~/Desktop/other/OTHER/zyquo-term` (2026-07-30):** |
| 64 | 89 | - Identity: `Developer ID Application: Simon-Pierre Boucher (3YM54G49SN)` (Team ID `3YM54G49SN`) |
added
scripts/notarize.sh
+106 −0
@@ -0,0 +1,106 @@ | ||
| 1 | +#!/bin/bash | |
| 2 | +# | |
| 3 | +# notarize.sh | |
| 4 | +# Zyquo Agent | |
| 5 | +# | |
| 6 | +# Author: Simon-Pierre Boucher | |
| 7 | +# Mail: contact@spboucher.ai | |
| 8 | +# | |
| 9 | +# Developer ID signing, notarization, stapling, and verification for | |
| 10 | +# Zyquo Agent. Identity, Team ID, and the notarytool keychain profile are the | |
| 11 | +# same ones the Zyquo family already uses (see ~/Desktop/other/OTHER/zyquo-term). | |
| 12 | +# | |
| 13 | +# Usage: notarize.sh <app-dir> <identity> <keychain-profile> <entitlements> | |
| 14 | +# notarize.sh <app-dir> <identity> <keychain-profile> <entitlements> dmg | |
| 15 | +# | |
| 16 | +# Signs nested code first, then the bundle, with the Hardened Runtime and a | |
| 17 | +# secure timestamp; submits a ditto zip to Apple, waits, staples the ticket, | |
| 18 | +# and fails loudly unless `spctl` reports "Notarized Developer ID". | |
| 19 | +# | |
| 20 | +# Entitlements posture (Resources/ZyquoAgent.entitlements): Hardened Runtime | |
| 21 | +# ON, App Sandbox deliberately OFF (Zyquo Agent must spawn /bin/bash and | |
| 22 | +# /usr/bin/osascript to do its job, like Terminal.app or the sibling Zyquo | |
| 23 | +# Term); the only declared entitlement is | |
| 24 | +# com.apple.security.automation.apple-events, paired with | |
| 25 | +# NSAppleEventsUsageDescription in Info.plist, for AppleScript automation. | |
| 26 | +# Keep that file pure ASCII with no XML comments: the AMFI parser codesign | |
| 27 | +# uses rejects both. | |
| 28 | +# | |
| 29 | +set -euo pipefail | |
| 30 | + | |
| 31 | +APP_DIR="${1:?app bundle path required}" | |
| 32 | +IDENTITY="${2:?signing identity required}" | |
| 33 | +KEYCHAIN_PROFILE="${3:?notarytool keychain profile required}" | |
| 34 | +ENTITLEMENTS="${4:?entitlements plist required}" | |
| 35 | +MAKE_DMG="${5:-}" | |
| 36 | + | |
| 37 | +APP_NAME="$(basename "$APP_DIR" .app)" | |
| 38 | +DIST="$(dirname "$APP_DIR")" | |
| 39 | +EXEC_NAME="ZyquoAgent" | |
| 40 | +ZIP_PATH="$DIST/${EXEC_NAME}.zip" | |
| 41 | +DMG_PATH="$DIST/${EXEC_NAME}.dmg" | |
| 42 | + | |
| 43 | +[ -d "$APP_DIR" ] || { echo "ERROR: $APP_DIR missing — run make release/bundle first" >&2; exit 1; } | |
| 44 | +[ -f "$ENTITLEMENTS" ] || { echo "ERROR: $ENTITLEMENTS missing" >&2; exit 1; } | |
| 45 | + | |
| 46 | +# ---------------------------------------------------------------- signing -- | |
| 47 | +echo "=== Signing (Developer ID, hardened runtime) ===" | |
| 48 | +# Nested code first (frameworks/bundles/helpers), then the main executable, | |
| 49 | +# then the bundle itself — codesign requires inside-out order. | |
| 50 | +while IFS= read -r -d '' nested; do | |
| 51 | + echo " nested: $nested" | |
| 52 | + codesign --force --options runtime --timestamp \ | |
| 53 | + --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$nested" | |
| 54 | +done < <(find "$APP_DIR/Contents" \ | |
| 55 | + \( -name '*.framework' -o -name '*.dylib' -o -name '*.bundle' \) -print0 2>/dev/null) | |
| 56 | + | |
| 57 | +codesign --force --options runtime --timestamp \ | |
| 58 | + --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" \ | |
| 59 | + "$APP_DIR/Contents/MacOS/$EXEC_NAME" | |
| 60 | +codesign --force --options runtime --timestamp \ | |
| 61 | + --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP_DIR" | |
| 62 | + | |
| 63 | +echo "=== Verifying signature ===" | |
| 64 | +codesign --verify --deep --strict --verbose=2 "$APP_DIR" | |
| 65 | +codesign --display --entitlements - --verbose=2 "$APP_DIR" 2>&1 | sed -n '1,12p' | |
| 66 | + | |
| 67 | +# ------------------------------------------------------------ notarizing -- | |
| 68 | +echo "=== Submitting to Apple notary service (profile: $KEYCHAIN_PROFILE) ===" | |
| 69 | +rm -f "$ZIP_PATH" | |
| 70 | +ditto -c -k --keepParent "$APP_DIR" "$ZIP_PATH" | |
| 71 | +xcrun notarytool submit "$ZIP_PATH" --keychain-profile "$KEYCHAIN_PROFILE" --wait | |
| 72 | + | |
| 73 | +echo "=== Stapling ===" | |
| 74 | +xcrun stapler staple "$APP_DIR" | |
| 75 | +xcrun stapler validate "$APP_DIR" | |
| 76 | + | |
| 77 | +echo "=== Gatekeeper assessment ===" | |
| 78 | +assessment="$(spctl -a -vv "$APP_DIR" 2>&1)" | |
| 79 | +echo "$assessment" | |
| 80 | +case "$assessment" in | |
| 81 | + *"source=Notarized Developer ID"*) | |
| 82 | + echo "✅ spctl: accepted, source=Notarized Developer ID" ;; | |
| 83 | + *) | |
| 84 | + echo "❌ spctl did not report a notarized Developer ID signature" >&2 | |
| 85 | + exit 1 ;; | |
| 86 | +esac | |
| 87 | + | |
| 88 | +# Re-zip the stapled bundle so the distributed archive carries the ticket. | |
| 89 | +rm -f "$ZIP_PATH" | |
| 90 | +ditto -c -k --keepParent "$APP_DIR" "$ZIP_PATH" | |
| 91 | +echo "Archive: $ZIP_PATH" | |
| 92 | + | |
| 93 | +# ------------------------------------------------------------- optional dmg -- | |
| 94 | +if [ "$MAKE_DMG" = "dmg" ]; then | |
| 95 | + echo "=== Building signed + notarized DMG ===" | |
| 96 | + rm -f "$DMG_PATH" | |
| 97 | + hdiutil create -volname "$APP_NAME" -srcfolder "$APP_DIR" \ | |
| 98 | + -ov -format UDZO "$DMG_PATH" | |
| 99 | + codesign --force --timestamp --sign "$IDENTITY" "$DMG_PATH" | |
| 100 | + xcrun notarytool submit "$DMG_PATH" --keychain-profile "$KEYCHAIN_PROFILE" --wait | |
| 101 | + xcrun stapler staple "$DMG_PATH" | |
| 102 | + spctl -a -vv -t open --context context:primary-signature "$DMG_PATH" 2>&1 | tail -3 | |
| 103 | + echo "DMG: $DMG_PATH" | |
| 104 | +fi | |
| 105 | + | |
| 106 | +echo "=== Done: $APP_DIR is signed, notarized, and stapled ===" | |
| 107 | ||