From 99419eb04ba97c50ae3368c1618f6e74e02dea40 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 05 Jun 2026 02:43:40 +0800
Subject: [PATCH] Record App Store quarantine rebuild

---
 02-P-NBL/freeze/20260605-mac-app-store-upload-preflight.md                               |   97 +++++++++++++++++++
 Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/RELEASE_MANIFEST.md             |   77 +++++++++++++++
 Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/SHA256SUMS.txt                  |    1 
 Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/APP_STORE_CONNECT_SUBMISSION.md |   37 +++++++
 Docs/KM2.项目管理-非业务直接相关/QA工作-经验与教训.md                                                      |    7 +
 02-P-NBL/freeze/20260605-mac-app-store-quarantine-hotfix.md                              |   39 +++++++
 6 files changed, 258 insertions(+), 0 deletions(-)

diff --git a/02-P-NBL/freeze/20260605-mac-app-store-quarantine-hotfix.md b/02-P-NBL/freeze/20260605-mac-app-store-quarantine-hotfix.md
new file mode 100644
index 0000000..76d66cd
--- /dev/null
+++ b/02-P-NBL/freeze/20260605-mac-app-store-quarantine-hotfix.md
@@ -0,0 +1,39 @@
+# 2026-06-05 Mac App Store quarantine xattr 修复
+
+## 结论
+
+- 版本: `2.1.28`
+- 新 build: `20260605.0236`
+- 源码 commit: `8cd205ee7e12a2b46d6beff6e5a6434c4862af24`
+- 修复原因: Transporter/TestFlight validation failed 91109。
+- 修复内容: 在 App Store 打包脚本中对 `.app` 递归执行 `xattr -cr`,并在 packaging 前断言不存在 `com.apple.quarantine`。
+
+## 产物
+
+- `/Users/ar/Desktop/PrivateVoice-26.0604上架/02-上传包/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg`
+- `/Users/ar/Projects/PrivateVoice/X-运营资料/PrivateVoice-26.0604上架/02-上传包/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg`
+- `/Users/ar/Projects/PrivateVoice/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg`
+
+SHA256:
+
+```text
+a0d01edf950ea69892b14c0f9cd989fc31d6927be4b26d31a4168526fdddd190  PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg
+```
+
+## 验证
+
+- `go test ./internal/startup ./internal/model ./internal/modelselection`: PASS
+- `bash -n privatevoice.src/scripts/build-macappstore-pkg-macos.sh`: PASS
+- arm64 App Store signed package build: PASS
+- x86_64 App Store signed package build: PASS
+- universal app merge and re-sign: PASS
+- `codesign -vvv --deep --strict` universal app: PASS
+- `pkgutil --check-signature` universal pkg: PASS
+- 展开 pkg 后读取 `Info.plist`,`LSApplicationCategoryType=public.app-category.productivity`: PASS
+- 展开 pkg 后读取签名 entitlements,`com.apple.application-identifier=CR3J54M8BQ.com.shanghai3168.privatevoicedictation`: PASS
+- 展开 pkg 后检查 `com.apple.quarantine`: PASS,无输出
+- 三处 `.pkg` SHA256 一致: PASS
+
+## 上传提示
+
+请在 Transporter 中上传 `20260605.0236` 这个新包,不要再上传 `20260605.0216`。
diff --git a/02-P-NBL/freeze/20260605-mac-app-store-upload-preflight.md b/02-P-NBL/freeze/20260605-mac-app-store-upload-preflight.md
new file mode 100644
index 0000000..da17992
--- /dev/null
+++ b/02-P-NBL/freeze/20260605-mac-app-store-upload-preflight.md
@@ -0,0 +1,97 @@
+# 2026-06-05 Mac App Store 上传教训与预检清单
+
+## 这次的问题
+
+今天上传成功前反复踩了三个坑:
+
+1. `CFBundleIdentifier` 与 Apple Developer profile 的 Bundle ID 不一致。
+2. `Info.plist` 缺少 Mac App Store 必填的 `LSApplicationCategoryType`。
+3. `.app` 嵌入的 provisioning profile 有 `com.apple.application-identifier`,但签名 entitlements 没有同一个值,触发 Transporter/TestFlight `90886`。
+4. 下载得到的 provisioning profile 带 `com.apple.quarantine` 扩展属性,复制进 `.app` 后被打进 `.pkg`,触发 Transporter/TestFlight `91109`。
+
+这些都不是业务功能问题,而是 Mac App Store 上传包的元数据和签名验收不完整。
+
+## 必须记住的规则
+
+- Transporter 不是完整的本地 QA 工具;不能把用户上传失败当成检查方式。
+- `pkgutil --check-signature` 只能证明 `.pkg` 被 Installer 证书签了,不能证明 App Store 会接受。
+- `codesign -vvv --deep --strict` 只能证明签名结构有效,不能证明 entitlements 与 embedded profile 一致。
+- 每次重传必须换新的 `CFBundleVersion` build 号。
+- 同一个 App Store 版本可以保持 `CFBundleShortVersionString=2.1.28`,但每个新上传包必须有新的 build。
+- 最终给用户上传的只能是 `*-universal-macappstore.pkg`,不能是 sandbox POC DMG,也不能是旧 build。
+
+## 上传前硬性预检
+
+在把 `.pkg` 交给用户上传前,必须对最终 `.pkg` 展开检查,而不是只检查 build 目录里的 `.app`。
+
+必须检查:
+
+- `CFBundleIdentifier=com.shanghai3168.privatevoicedictation`
+- `CFBundleShortVersionString` 与本次发布版本一致
+- `CFBundleVersion` 是本次新 build
+- `LSApplicationCategoryType=public.app-category.productivity`
+- embedded provisioning profile 的 App ID 是 `CR3J54M8BQ.com.shanghai3168.privatevoicedictation`
+- signed entitlements 的 `com.apple.application-identifier` 是 `CR3J54M8BQ.com.shanghai3168.privatevoicedictation`
+- signed entitlements 的 `com.apple.developer.team-identifier` 是 `CR3J54M8BQ`
+- signed entitlements 包含 `com.apple.security.app-sandbox=true`
+- app 主程序是 universal `arm64 + x86_64`
+- `libsherpa-onnx-c-api.dylib` 是 universal `arm64 + x86_64`
+- `libonnxruntime.1.24.4.dylib` 是 universal `arm64 + x86_64`
+- `codesign -vvv --deep --strict` 对最终 app PASS
+- `pkgutil --check-signature` 对最终 pkg PASS
+- 最终 `.pkg` 展开后的 `.app` 内没有 `com.apple.quarantine` 扩展属性
+- release、桌面上传目录、运营资料目录里的 pkg SHA256 完全一致
+
+## 参考检查命令
+
+```bash
+PKG="/path/to/PrivateVoice-Dictation-2.1.28-buildYYYYMMDD.HHMM-universal-macappstore.pkg"
+TMP="/tmp/privatevoice-pkg-check"
+
+rm -rf "$TMP"
+pkgutil --expand-full "$PKG" "$TMP"
+APP="$(find "$TMP" -name 'PrivateVoice Dictation.app' -type d -print -quit)"
+
+/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Contents/Info.plist"
+/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist"
+/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$APP/Contents/Info.plist"
+/usr/libexec/PlistBuddy -c 'Print :LSApplicationCategoryType' "$APP/Contents/Info.plist"
+
+PROFILE_PLIST="$TMP/profile.plist"
+security cms -D -i "$APP/Contents/embedded.provisionprofile" > "$PROFILE_PLIST"
+/usr/libexec/PlistBuddy -c 'Print :Entitlements:com.apple.application-identifier' "$PROFILE_PLIST"
+/usr/libexec/PlistBuddy -c 'Print :Entitlements:com.apple.developer.team-identifier' "$PROFILE_PLIST"
+
+SIGNED_ENTITLEMENTS="$TMP/signed-entitlements.plist"
+codesign -d --entitlements :- "$APP" > "$SIGNED_ENTITLEMENTS" 2>/dev/null
+/usr/libexec/PlistBuddy -c 'Print :com.apple.application-identifier' "$SIGNED_ENTITLEMENTS"
+/usr/libexec/PlistBuddy -c 'Print :com.apple.developer.team-identifier' "$SIGNED_ENTITLEMENTS"
+/usr/libexec/PlistBuddy -c 'Print :com.apple.security.app-sandbox' "$SIGNED_ENTITLEMENTS"
+
+file "$APP/Contents/MacOS/PrivateVoice Dictation"
+file "$APP/Contents/Frameworks/libsherpa-onnx-c-api.dylib"
+file "$APP/Contents/Frameworks/libonnxruntime.1.24.4.dylib"
+
+codesign -vvv --deep --strict "$APP"
+pkgutil --check-signature "$PKG"
+xattr -lr "$APP" | grep -F 'com.apple.quarantine' && exit 1 || true
+shasum -a 256 "$PKG"
+```
+
+## 今天形成的修复
+
+- 正式 Bundle ID 固定为 `com.shanghai3168.privatevoicedictation`。
+- Mac App Store 分类固定为 `public.app-category.productivity`。
+- 打包脚本必须从 provisioning profile 合并以下 entitlements 后再签名:
+  - `com.apple.application-identifier`
+  - `com.apple.developer.team-identifier`
+  - `keychain-access-groups`
+- 当前可上传包是 `20260605.0216`,不要再上传 `20260604.0049` 或 `20260604.2353`。
+- 如果遇到 `91109`,必须在组装 app 后、签名后、productbuild 前对 `.app` 执行 `xattr -cr`,并展开最终 `.pkg` 检查没有 `com.apple.quarantine`。
+
+## 上传后还要检查
+
+- App Store Connect 里选择最新 build。
+- 付费应用必须确认 Paid Apps Agreement、税务和银行信息已生效。
+- 价格不在 Transporter 设置;在 App Store Connect 的 `Monetization / Pricing and Availability` 设置。
+- 提交审核前确认截图、隐私政策、支持 URL、审核备注、隐私问卷和价格都已填完。
diff --git "a/Docs/KM2.\351\241\271\347\233\256\347\256\241\347\220\206-\351\235\236\344\270\232\345\212\241\347\233\264\346\216\245\347\233\270\345\205\263/QA\345\267\245\344\275\234-\347\273\217\351\252\214\344\270\216\346\225\231\350\256\255.md" "b/Docs/KM2.\351\241\271\347\233\256\347\256\241\347\220\206-\351\235\236\344\270\232\345\212\241\347\233\264\346\216\245\347\233\270\345\205\263/QA\345\267\245\344\275\234-\347\273\217\351\252\214\344\270\216\346\225\231\350\256\255.md"
new file mode 100644
index 0000000..8d7307b
--- /dev/null
+++ "b/Docs/KM2.\351\241\271\347\233\256\347\256\241\347\220\206-\351\235\236\344\270\232\345\212\241\347\233\264\346\216\245\347\233\270\345\205\263/QA\345\267\245\344\275\234-\347\273\217\351\252\214\344\270\216\346\225\231\350\256\255.md"
@@ -0,0 +1,7 @@
+## 2026-06-05|Mac App Store 上传前必须做包内验收
+
+- 触发:用户指出 App Store 上传反复失败,先后缺少分类键、签名 application identifier 等项,导致多次重新上传。
+- 失误:AI 只做了本地构建、签名和 pkg 签名校验,没有把最终 `.pkg` 展开后按 App Store/TestFlight 规则逐项核对 `Info.plist`、embedded profile 和 signed entitlements。
+- 原因:发布前 QA 门禁没有覆盖 Apple Transporter 的关键校验项,也没有形成固定预检清单。
+- 改进:以后 Mac App Store 上传包交付前,必须展开最终 `.pkg`,检查 Bundle ID、版本/build、`LSApplicationCategoryType`、profile App ID、signed `com.apple.application-identifier`、Team ID、sandbox entitlements、universal 架构、pkg 签名和包内无 `com.apple.quarantine` 扩展属性全部通过后,才交给用户上传。
+- 关联:`privatevoice.src/scripts/build-macappstore-pkg-macos.sh`、`02-P-NBL/freeze/20260605-mac-app-store-upload-preflight.md`。
diff --git a/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/APP_STORE_CONNECT_SUBMISSION.md b/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/APP_STORE_CONNECT_SUBMISSION.md
new file mode 100644
index 0000000..7837687
--- /dev/null
+++ b/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/APP_STORE_CONNECT_SUBMISSION.md
@@ -0,0 +1,37 @@
+# App Store Connect Submission Prep
+
+## Build
+
+- App: 私语输入法 / PrivateVoice Dictation
+- Bundle ID: `com.shanghai3168.privatevoicedictation`
+- Version: `2.1.28`
+- Build: `20260605.0236`
+- Platform: macOS
+- Primary category: Productivity
+- `LSApplicationCategoryType`: `public.app-category.productivity`
+- Signed application identifier: `CR3J54M8BQ.com.shanghai3168.privatevoicedictation`
+- Team ID: `CR3J54M8BQ`
+- Minimum macOS for App Store sandbox build: `13.4`
+- Support URL: `https://shanghai3168.github.io/privatevoice-dictation-support/support.html`
+- Privacy Policy URL: `https://shanghai3168.github.io/privatevoice-dictation-support/privacy.html`
+
+## Current Upload Package
+
+```text
+/Users/ar/Desktop/PrivateVoice-26.0604上架/02-上传包/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg
+```
+
+This replaces the failed `20260605.0216` package. The new package removes `com.apple.quarantine` extended attributes from the app bundle before packaging.
+
+## Validation Evidence
+
+- Universal `arm64` + `x86_64` app executable and bundled dylibs: PASS.
+- App code signature: PASS.
+- Installer package signature: PASS.
+- Expanded package signed app entitlements include `com.apple.application-identifier=CR3J54M8BQ.com.shanghai3168.privatevoicedictation`: PASS.
+- Expanded package contains no `com.apple.quarantine` extended attribute: PASS.
+- SHA256: `a0d01edf950ea69892b14c0f9cd989fc31d6927be4b26d31a4168526fdddd190`.
+
+## Upload
+
+Open Transporter, remove the failed previous package, drag in the package above, and click `Deliver`.
diff --git a/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/RELEASE_MANIFEST.md b/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/RELEASE_MANIFEST.md
new file mode 100644
index 0000000..8e40239
--- /dev/null
+++ b/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/RELEASE_MANIFEST.md
@@ -0,0 +1,77 @@
+# Release Manifest: PrivateVoice Dictation 2.1.28 build 20260605.0236
+
+## Package Type
+
+- Type: Mac App Store signed universal upload package
+- Platform: macOS universal, `arm64` + `x86_64`
+- Signing: Apple App Store distribution signing with embedded Mac App Store provisioning profile
+- Generated at: 2026-06-04T18:42:19Z
+- Operator: Codex
+
+## Source
+
+- Repository: `/Users/ar/Projects/PrivateVoice`
+- Branch: `codex/privatevoice-freeze-2.1.25`
+- Build source commit: `8cd205ee7e12a2b46d6beff6e5a6434c4862af24`
+- Version: `2.1.28`
+- Build: `20260605.0236`
+- Bundle ID: `com.shanghai3168.privatevoicedictation`
+- App Store category UTI: `public.app-category.productivity`
+
+## Scope
+
+- Fixed Transporter/TestFlight validation failure 91109.
+- The downloaded provisioning profile carried `com.apple.quarantine`, which was copied into the app bundle and then into the package.
+- Updated `privatevoice.src/scripts/build-macappstore-pkg-macos.sh` to recursively clear extended attributes from the app bundle before and after signing.
+- Added build-time assertion that no `com.apple.quarantine` attributes remain before packaging.
+- Rebuilt the Mac App Store upload package with a new build number.
+
+## Build Commands
+
+```bash
+git diff --check -- privatevoice.src/scripts/build-macappstore-pkg-macos.sh privatevoice.src/app.go privatevoice.src/build/darwin/Info.plist
+bash -n privatevoice.src/scripts/build-macappstore-pkg-macos.sh
+go test ./internal/startup ./internal/model ./internal/modelselection
+./privatevoice.src/scripts/build-macappstore-pkg-macos.sh arm64
+./privatevoice.src/scripts/build-macappstore-pkg-macos.sh x86_64
+```
+
+The final universal app was assembled from the signed arm64 and x86_64 app bundles with `lipo`, then re-signed with the merged App Store entitlements, stripped of extended attributes, and packaged with `productbuild`.
+
+## Verification
+
+- `go test ./internal/startup ./internal/model ./internal/modelselection`: PASS.
+- `bash -n privatevoice.src/scripts/build-macappstore-pkg-macos.sh`: PASS.
+- `npm run build`: PASS, with existing Svelte a11y warnings in `UpdateDialog.svelte` and `ToggleSwitch.svelte`.
+- arm64 Mac App Store package build and signature verification: PASS.
+- x86_64 Mac App Store package build and signature verification: PASS.
+- Universal app executable contains `arm64` and `x86_64`: PASS.
+- Bundled `libsherpa-onnx-c-api.dylib` contains `arm64` and `x86_64`: PASS.
+- Bundled `libonnxruntime.1.24.4.dylib` contains `arm64` and `x86_64`: PASS.
+- Universal app `codesign -vvv --deep --strict`: PASS.
+- Universal package `pkgutil --check-signature`: PASS.
+- Expanded package app `Info.plist` contains `CFBundleVersion=20260605.0236`: PASS.
+- Expanded package app `Info.plist` contains `LSApplicationCategoryType=public.app-category.productivity`: PASS.
+- Expanded package app signed entitlements contain `com.apple.application-identifier=CR3J54M8BQ.com.shanghai3168.privatevoicedictation`: PASS.
+- Expanded package app signed entitlements contain `com.apple.developer.team-identifier=CR3J54M8BQ`: PASS.
+- Expanded package app signed entitlements contain `com.apple.security.app-sandbox=true`: PASS.
+- Expanded package app contains no `com.apple.quarantine` extended attribute: PASS.
+- Release, desktop, and operations package SHA256 match: PASS.
+
+## Artifact
+
+```text
+Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg
+/Users/ar/Desktop/PrivateVoice-26.0604上架/02-上传包/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg
+/Users/ar/Projects/PrivateVoice/X-运营资料/PrivateVoice-26.0604上架/02-上传包/PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg
+```
+
+SHA256:
+
+```text
+a0d01edf950ea69892b14c0f9cd989fc31d6927be4b26d31a4168526fdddd190  PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg
+```
+
+## Upload Note
+
+Use the `20260605.0236` package for Transporter. Do not upload the previous `20260605.0216` package, because it contains `com.apple.quarantine` extended attributes inside the app payload.
diff --git a/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/SHA256SUMS.txt b/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/SHA256SUMS.txt
new file mode 100644
index 0000000..0347329
--- /dev/null
+++ b/Release/PrivateVoice-Dictation-2.1.28-build20260605.0236/SHA256SUMS.txt
@@ -0,0 +1 @@
+a0d01edf950ea69892b14c0f9cd989fc31d6927be4b26d31a4168526fdddd190  PrivateVoice-Dictation-2.1.28-build20260605.0236-universal-macappstore.pkg

--
Gitblit v1.9.3