SPB Git

spb/zyquo-atlas

Public License

The AI-native macOS web browser — every surface, intelligent.

phase8: Developer ID signing + notarization — notarize.sh (inside-out sign, notarytool --wait, staple, spctl), universal single-command build, Hardened Runtime entitlements; make release → Notarized Developer ID (verified). All 9 phases complete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simon-pierre boucher committed (Jul 31, 2026) parent ebc9f96 Browse files

6 changed files +117 −29

modified Makefile +7 −15
@@ -59,28 +59,20 @@ bundle:
59 @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done 59 @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done
60 scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)" 60 scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)"
61 61
62−# Both arch builds land in .build/out/Products/Release, so each slice is copied 62+# SwiftPM builds a fat binary in one command (arm64 + x86_64); products land in
63−# aside before lipo. The -U flag allows the swiftCompatibility56 force-load 63+# .build/apple/Products/Release. Verifies the slices with lipo.
64−# symbol (CLT ships it arm64-only; never referenced at runtime on macOS 13+). 64+PRODUCTS := .build/apple/Products/Release
65 universal: 65 universal:
66 @echo "=== Building universal binary (arm64 + x86_64) ===" 66 @echo "=== Building universal binary (arm64 + x86_64) ==="
67− mkdir -p .build/universal 67+ swift build -c release --arch arm64 --arch x86_64
68− swift build -c release --arch arm64 68+ lipo -archs "$(PRODUCTS)/$(EXEC_NAME)"
69− cp .build/out/Products/Release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).arm64
70− swift build -c release --arch x86_64 -Xlinker -U -Xlinker '__swift_FORCE_LOAD_$$_swiftCompatibility56'
71− cp .build/out/Products/Release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).x86_64
72− lipo -create \
73− .build/universal/$(EXEC_NAME).arm64 \
74− .build/universal/$(EXEC_NAME).x86_64 \
75− -output .build/universal/$(EXEC_NAME)
76− lipo -archs .build/universal/$(EXEC_NAME)
77 69
78 release: universal 70 release: universal
79 @echo "=== Assembling $(APP_DIR) (universal) ===" 71 @echo "=== Assembling $(APP_DIR) (universal) ==="
80 rm -rf "$(APP_DIR)" 72 rm -rf "$(APP_DIR)"
81 mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources" 73 mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources"
82− cp .build/universal/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)" 74+ cp "$(PRODUCTS)/$(EXEC_NAME)" "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)"
83− @for b in .build/out/Products/Release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done 75+ @for b in "$(PRODUCTS)"/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done
84 @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi 76 @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi
85 @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done 77 @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done
86 scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)" 78 scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)"
modified README.md +9 −3
@@ -18,12 +18,18 @@ surface, powered by the same providers/models and encrypted key vault as
18 ```sh 18 ```sh
19 make dev # release build → dist/Zyquo Atlas.app, ad-hoc signed (local iteration) 19 make dev # release build → dist/Zyquo Atlas.app, ad-hoc signed (local iteration)
20 make run # make dev, then launch the app 20 make run # make dev, then launch the app
21−make test # run the test suite (Command Line Tools only) 21+make test # run the test suite
22−make release # universal, Developer ID signed, notarized, stapled (Phase 8) 22+make release # universal (arm64+x86_64), Developer ID signed, notarized, stapled
23−make verify # Phase 7 API verification with real keys (source .env.keys) 23+make icon # regenerate AppIcon.icns from assets/icon/zyquo-atlas.svg
24+make verify # API verification with real keys (source .env.keys)
24 make clean 25 make clean
25 ``` 26 ```
26 27
28+`make release` produces a **Notarized Developer ID** `Zyquo Atlas.app` (verified:
29+`spctl -a -vv` → `source=Notarized Developer ID`), universal, built without the
30+Xcode IDE. Signing identity, Team ID, and the `notarytool` keychain profile are
31+reused from the Zyquo family pipeline; no secrets live in this repo.
32+
27 ### Toolchain note 33 ### Toolchain note
28 34
29 The Command Line Tools' default SDK (macOS 27 beta) declares SwiftUI's `@State` 35 The Command Line Tools' default SDK (macOS 27 beta) declares SwiftUI's `@State`
modified Resources/ZyquoAtlas.entitlements +10 −7
@@ -7,16 +7,19 @@
7 Author: Simon-Pierre Boucher 7 Author: Simon-Pierre Boucher
8 Mail: contact@spboucher.ai 8 Mail: contact@spboucher.ai
9 9
10− Phase 8 finalizes the Hardened Runtime posture. A WKWebView browser uses 10+ Signed with the Hardened Runtime (Developer ID distribution). Zyquo Atlas is
11− WebKit XPC/child processes; the minimal set that lets WKWebView, downloads, 11+ a WKWebView browser: WebKit runs page JavaScript in Apple's own out-of-process
12− and the app's own HTTPS API calls work is chosen and validated in Phase 8. 12+ WebContent/Networking/GPU XPC services (WebKit.framework is a *system*
13− Not App-Sandboxed in v1 (Developer ID distribution, not Mac App Store). 13+ framework, not embedded), so the host app needs no JIT or network
14+ entitlement for WKWebView, downloads, or its own HTTPS API calls to function.
15+ Not App-Sandboxed (like Safari-alternative browsers and terminals); a browser
16+ loads arbitrary content and manages its own data outside a container. This is
17+ the minimal posture that notarizes and runs — matching the Zyquo family
18+ pipeline (zyquo-term).
14 --> 19 -->
15 <plist version="1.0"> 20 <plist version="1.0">
16 <dict> 21 <dict>
17 <key>com.apple.security.cs.allow-jit</key> 22 <key>com.apple.security.cs.allow-jit</key>
18− <true/> 23+ <false/>
19− <key>com.apple.security.network.client</key>
20− <true/>
21 </dict> 24 </dict>
22 </plist> 25 </plist>
modified docs/PLAN.md +29 −0
@@ -165,3 +165,32 @@ launchable, single window; browser core is Phase 2).
165 **Failures found & fixed:** first sweep 173/184 → three genuine failures fixed — two reasoning models (Together `Qwen3.5-9B`, DeepInfra `Kimi-K2.5`) were unflagged in the catalog and exhausted the token budget on hidden thinking → flagged `reasoning` + raised the reasoning token budget (harness 8000, AIService 8192, a real product fix so reasoning models reach their answer); one non-functional model (Together `google/gemma-4-31B-it`, streams empty) removed from the catalog (now **169 models**). Remaining failures were **transient provider-side network errors** (Cerebras/DeepInfra/Together 503/timeout during concurrent bursts) that all cleared on retry — recorded in `docs/VERIFICATION.md` in their re-verified green state. 165 **Failures found & fixed:** first sweep 173/184 → three genuine failures fixed — two reasoning models (Together `Qwen3.5-9B`, DeepInfra `Kimi-K2.5`) were unflagged in the catalog and exhausted the token budget on hidden thinking → flagged `reasoning` + raised the reasoning token budget (harness 8000, AIService 8192, a real product fix so reasoning models reach their answer); one non-functional model (Together `google/gemma-4-31B-it`, streams empty) removed from the catalog (now **169 models**). Remaining failures were **transient provider-side network errors** (Cerebras/DeepInfra/Together 503/timeout during concurrent bursts) that all cleared on retry — recorded in `docs/VERIFICATION.md` in their re-verified green state.
166 166
167 **Final: 183 checks · 183 passed · 0 failed.** No keys or browsing data committed. **Phase 7 gate PASSED.** 167 **Final: 183 checks · 183 passed · 0 failed.** No keys or browsing data committed. **Phase 7 gate PASSED.**
168+
169+## Phase 8 — Signing & Notarization — COMPLETE (Notarized Developer ID)
170+
171+- [x] Inspected `zyquo-term` signing setup and reused it verbatim: identity `Developer ID Application: Simon-Pierre Boucher (3YM54G49SN)`, Team `3YM54G49SN`, notarytool keychain profile `MacLustr-Notarize` (both confirmed present/live on this machine)
172+- [x] `Resources/ZyquoAtlas.entitlements` — Hardened Runtime posture; **no special entitlements needed** because WKWebView runs page JS in Apple's system WebContent XPC processes (WebKit.framework is not embedded); not App-Sandboxed (browser). Minimal, matches the family.
173+- [x] `scripts/notarize.sh` — signs inside-out (nested SPM resource bundle, then the app) with `--options runtime --timestamp --entitlements`, `ditto -c -k --keepParent` zip, `notarytool submit --wait`, `stapler staple` + `validate`, `spctl` assessment
174+- [x] Makefile `universal` fixed to SwiftPM single-command multi-arch (`swift build -c release --arch arm64 --arch x86_64` → fat binary in `.build/apple/Products/Release`; the old two-build+lipo produced two arm64 slices under the now-installed Xcode 26.6 toolchain)
175+- [x] `make release` run end-to-end: **Apple notarization status: Accepted** (submission `2003be13-…`), stapled + validated
176+
177+**Verified independently:** `lipo -archs` → `x86_64 arm64`; `codesign -dvv` → `flags=0x10000(runtime)`, Authority `Developer ID Application …`, `TeamIdentifier=3YM54G49SN`; `stapler validate` → worked; **`spctl -a -vv` → `accepted, source=Notarized Developer ID`**. **Phase 8 gate PASSED.**
178+
179+Notes: `make dev` (ad-hoc) retained for iteration. Optional signed+stapled DMG is an easy follow-up (the app is already notarized+stapled). Vendored `Content/Readability*.js` (Mozilla, Apache-2.0, unmodified) keep Mozilla's copyright header rather than the Atlas author header — correct for license integrity; attribution in `Content/LICENSE-Readability.txt`.
180+
181+---
182+
183+## Definition of Done — final check (2026-07-30)
184+
185+- [x] `make release` → Developer ID–signed, **notarized, stapled** `Zyquo Atlas.app`, universal, built without the Xcode IDE (SPM/CLI); `spctl` = Notarized Developer ID
186+- [x] Fast multi-tab WKWebView browser: profiles, private mode, favorites, full-text history, downloads, find, reader
187+- [x] AI everywhere: omnibox ask, chat-with-page, selection actions, summarize/translate/rewrite, multi-tab compare — streaming, grounded, every provider/model (Phase 7 table green, 169 models)
188+- [x] Theming/customization engine: themes, backgrounds, top/left tabs, density, custom start page — live, per profile
189+- [x] Privacy model: page content leaves only on user action; keys in the reused AES-256-GCM vault (no Keychain)
190+- [x] Teal-indigo "atlas" SVG icon → `.icns` + template glyph; sibling of Cloud/Local/Agent
191+- [x] Light theme matches the Phase 4 spec; dark derived
192+- [x] Naming coherent (`Zyquo Atlas`, `com.zyquo.atlas`, `ZyquoAtlas`); every authored code file carries the header (verified)
193+- [x] `docs/PLAN.md` all phases complete; `AI-BROWSER-RESEARCH.md`, `PROVIDER-REUSE.md`, `VERIFICATION.md` complete
194+- [~] Deferred enhancements documented in Phase 6 (tab groups/drag-reorder gesture, hover thumbnails, in-field writing-assist, auto/hover summaries, citation-chip rendering, menu-bar extra)
195+
196+**All 9 phases (0–8) complete. Zyquo Atlas builds, verifies green, and ships as a notarized Developer ID app.**
modified docs/VERIFICATION.md +4 −4
@@ -18,8 +18,8 @@ come from the environment and are never logged.
18 18
19 | Source | Page | Test | Result | Latency | Detail | 19 | Source | Page | Test | Result | Latency | Detail |
20 |---|---|---|---|---|---| 20 |---|---|---|---|---|---|
21−| — | `article` | extract | ✅ | 2.3s | quality=reader, words=9210, ~17278tok, headings=27 | 21+| — | `article` | extract | ✅ | 1.9s | quality=reader, words=9210, ~17278tok, headings=27 |
22 | — | `docs` | extract | ✅ | 1.7s | quality=reader, words=83, ~190tok, headings=0 | 22 | — | `docs` | extract | ✅ | 1.7s | quality=reader, words=83, ~190tok, headings=0 |
23−| — | `js-app` | extract | ✅ | 1.9s | quality=reader, words=1174, ~2830tok, headings=23 | 23+| — | `js-app` | extract | ✅ | 1.8s | quality=reader, words=1174, ~2830tok, headings=23 |
24−| — | `long-article` | extract | ✅ | 1.9s | quality=reader, words=28685, ~49595tok, headings=40 | 24+| — | `long-article` | extract | ✅ | 1.8s | quality=reader, words=28685, ~49595tok, headings=40 |
25−| — | `selection` | extract | ✅ | 2.4s | selection=In cartography, a map is a two-dimension, words=18106 | 25+| — | `selection` | extract | ✅ | 2.1s | selection=In cartography, a map is a two-dimension, words=18106 |
added scripts/notarize.sh +58 −0
@@ -0,0 +1,58 @@
1+#!/bin/bash
2+#
3+# notarize.sh
4+# Zyquo Atlas
5+#
6+# Author: Simon-Pierre Boucher
7+# Mail: contact@spboucher.ai
8+#
9+# Developer ID signing + notarization + stapling for an assembled
10+# Zyquo Atlas.app (Phase 8). Identity, Team ID, and the notarytool keychain
11+# profile are reused from the Zyquo family pipeline (zyquo-term).
12+#
13+# Usage: notarize.sh <app-dir> <identity> <notary-profile> <entitlements>
14+# Signs inside-out (nested resource bundles, then the app) with the Hardened
15+# Runtime, zips with ditto, submits with notarytool --wait, staples, and
16+# verifies spctl == "Notarized Developer ID". Secrets are never printed.
17+#
18+set -euo pipefail
19+cd "$(dirname "$0")/.."
20+
21+APP_DIR="$1"
22+IDENTITY="$2"
23+KEYCHAIN_PROFILE="$3"
24+ENTITLEMENTS="$4"
25+ZIP_PATH="${APP_DIR%.app}.zip"
26+
27+[ -d "$APP_DIR" ] || { echo "ERROR: $APP_DIR missing — run 'make release' (assembles the bundle first)" >&2; exit 1; }
28+
29+echo "=== Signing (Developer ID, Hardened Runtime) ==="
30+# Sign nested code first (SPM resource bundles carrying the Content/*.js and
31+# icon), inside-out, so the outer signature stays valid.
32+find "$APP_DIR/Contents" -name "*.bundle" -maxdepth 3 -print0 2>/dev/null \
33+ | while IFS= read -r -d '' bundle; do
34+ echo " nested: $(basename "$bundle")"
35+ codesign --force --options runtime --timestamp \
36+ --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$bundle"
37+ done
38+# Sign the app bundle (also signs Contents/MacOS/ZyquoAtlas).
39+codesign --force --options runtime --timestamp \
40+ --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP_DIR"
41+codesign --verify --deep --strict --verbose=2 "$APP_DIR"
42+echo "Signature valid."
43+
44+echo "=== Packaging for notarization (ditto) ==="
45+rm -f "$ZIP_PATH"
46+ditto -c -k --keepParent "$APP_DIR" "$ZIP_PATH"
47+
48+echo "=== Submitting to notarytool (profile: $KEYCHAIN_PROFILE) ==="
49+xcrun notarytool submit "$ZIP_PATH" --keychain-profile "$KEYCHAIN_PROFILE" --wait
50+
51+echo "=== Stapling ==="
52+xcrun stapler staple "$APP_DIR"
53+xcrun stapler validate "$APP_DIR"
54+
55+echo "=== Gatekeeper assessment ==="
56+spctl -a -vvv --type execute "$APP_DIR"
57+
58+echo "Done: $APP_DIR is Developer ID–signed, notarized, and stapled."
59
Menu

Theme