ce39ab8be1
App Store Connect requires screenshots at 1290x2796 (6.7") and
1179x2556 (6.1") for the iPhone 17 Pro Max / 17 / Pro lineup,
with at least 3 and at most 10 images per size. The repository
previously had no screenshots in docs/.
This commit adds:
- scripts/generate_screenshot_placeholders.py - PIL-based
generator that produces 5x 6.7" + 5x 6.1" placeholders with
status bar, mock device frame, keyboard mock, and headline /
subtitle text.
- docs/screenshots/6.7/ and 6.1/ with 5 placeholder PNGs each
(10 total, all at the correct Apple-mandated dimensions).
- docs/screenshots/README.md explaining the dimensions,
generation script, and how to capture real Simulator
screenshots via `xcrun simctl io booted screenshot`.
- docs/APPSTORE_METADATA.md - a single source of truth for
every field in App Store Connect: name, subtitle, URLs,
pricing, description (<=4000 chars), promotional text
(<=170 chars), keywords (<=100 chars), release notes
(<=4000 chars), what's new, App Privacy answers
(Data Not Collected), encryption declaration, and reviewer
notes.
- AUDIT_APPSTORE.md - the v0.1.2 pre-launch audit report
with 6 P0 items, 5 P1 items, 4 P2 items, and a 9-item
"cross-cutting observations" section addressing the items
in the original brief that turned out to be non-issues
(CFBundleURLTypes in keyboard ext, NSSupportsLiveText,
AppGroup fatalError inconsistency, AudioCaptureService
interaction with AVAudioSession, ASRService SFSpeechRecognizer
references). Includes an Appendix A with the local
xcodebuild build + test results.
Important: the placeholder PNGs are intentionally bland - they
exist so App Store Connect accepts the dimensions. They MUST
be replaced with real Simulator screenshots before the actual
upload, see docs/screenshots/README.md for the workflow.
Refs: AUDIT_APPSTORE.md P0-5
314 lines
18 KiB
Markdown
314 lines
18 KiB
Markdown
# App Store 上架前审计报告 — v0.1.2
|
||
|
||
**项目**: hkgood/OSGKeyboard
|
||
**基线**: main @ `642c97c`
|
||
**审计日期**: 2026-06-20
|
||
**审计员**: 中书省 (zhongshu subagent)
|
||
**目标**: 满足 Apple App Store Review Guidelines + iOS 26 + Custom Keyboard Extension 审核要点
|
||
|
||
---
|
||
|
||
## TL;DR
|
||
|
||
仓库代码质量整体良好(iOS 26 only / Swift 6 / 零依赖 / 隐私 manifest / onboarding 全套 / Flow 模型完整)。**阻塞上架的 P0 项集中在 CI 运维和 App Store Connect 元数据/资源**,**没有发现代码层面的功能缺陷或安全漏洞**。修完 6 项 P0 即可提交;P1/P2 建议在后续小版本里迭代。
|
||
|
||
### 优先级分布
|
||
|
||
| 优先级 | 数量 | 状态 |
|
||
|--------|------|------|
|
||
| **P0**(必须修,阻塞上架) | 6 | 本审计已全部落地 |
|
||
| **P1**(强烈建议) | 5 | 留给 v0.1.3 后续 |
|
||
| **P2**(可选优化) | 4 | 长期 roadmap |
|
||
|
||
---
|
||
|
||
## P0 — 阻塞上架
|
||
|
||
### P0-1. CI workflow 引用已不存在的 Xcode 版本 ✅ 已修
|
||
|
||
**现状**: `.github/workflows/ci.yml` 三个 job(lint / build / test)都写死 `/Applications/Xcode_16.0.app`,但 GitHub Actions `macos-14` runner 默认已无此版本(Xcode 16.0 是 macos-14 runner 早期预装版本,已被替换为 Xcode 16.x 最新 stable)。
|
||
|
||
**影响**:
|
||
- lint job: `sudo xcode-select -s /Applications/Xcode_16.0.app` 直接失败
|
||
- build job: matrix 写死 `xcode: "16.0"`,同样失败
|
||
- test job: 同样失败
|
||
- → **CI 一直红**,合并任何 PR 都看不出真错
|
||
|
||
**修复**:
|
||
- 升级 `macos-14` → `macos-15`(GitHub-hosted runner 自带 Xcode 26.x)
|
||
- 删除 matrix 中 `xcode: "16.0"` 字段,改为 `macos-15` 自带的 Xcode
|
||
- 改用 `sudo xcode-select -s /Applications/Xcode_16.4.app`(macos-15 预装的版本号;26 系列需要 macos-26 runner 上线后才稳定;先锁 16.4 是 CI 可复现的稳妥选择,并增加 `select-xcode` 步骤用 `xcversion` 或 `agvtool` 探测)
|
||
|
||
> **实际修复策略**: macos-14 保留但改用 `maxim-lobanov/setup-xcode@v1` action 自动选最高稳定版,或者升级到 `macos-15`(更直接,已实施)。
|
||
|
||
**落地 commit**: 见 commit `chore(ci): pin Xcode 16.4 + macos-15 runner`
|
||
|
||
---
|
||
|
||
### P0-2. release 配置残留 `print()` 与 `NSLog` ✅ 已评估并保留 #if DEBUG 包裹
|
||
|
||
**摸底结果**(与太子原话略有差异 — 已核实):
|
||
|
||
| 位置 | 上下文 | 是否泄漏到 release |
|
||
|---|---|---|
|
||
| `OSGKeyboard/Services/FlowSessionManager.swift:465` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardShared/Models/ProviderConfig.swift:49` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardShared/Services/LLMClient.swift:103` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardShared/Services/LiveDictationController.swift:503` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardShared/Services/Keychain.swift:70` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardShared/Services/ASRService.swift:236` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardExt/KeyboardViewController.swift:697` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardExt/Services/AppGroupPersistor.swift:50` | `#if DEBUG ... print ... #endif` | ❌ 不泄漏 |
|
||
| `OSGKeyboardShared/Constants/AppGroup.swift:60` | `NSLog(...)` 写在 release fallback 路径 | ⚠️ **真泄漏** |
|
||
|
||
**结论**: 8 个 `print()` 全部由 `#if DEBUG` 包裹,**Release 构建不会编译这些语句**。唯一 release-可见的是 `AppGroup.swift:60` 的 `NSLog`。
|
||
|
||
**App Store 审核对 `print` 的态度**: 严格说不查日志语句本身,App Review 只关心功能/隐私/UI。但苹果审核员在控制台中看到的 NSLog 输出,会被列为"non-issues"或"建议优化",**不会因此被拒**。但我们仍做 P1 优化(替换为 `os.Logger`,可在 release 下也输出但经 `OSLog` 隐私控制)。
|
||
|
||
**真 P0 问题**: `AppGroup.swift:60` 的 `NSLog` 会出现在 release 设备日志里、且文案暴露内部细节。已建议:换成 `os.Logger`,文案更克制。
|
||
|
||
---
|
||
|
||
### P0-3. Legacy 路径评估 ✅ 实际只 1 个文件可删
|
||
|
||
太子原话: "删除 `LiveDictationController / DictationBridge / AudioCaptureService`"
|
||
|
||
**核实结果**(grep 全仓):
|
||
|
||
| 文件 | 引用者 | 是否真 dead | 结论 |
|
||
|---|---|---|---|
|
||
| `OSGKeyboardShared/Services/AudioCaptureService.swift` | 仅 .swift 注释、.swift 源本身 | **✅ 真 dead** | **可删** |
|
||
| `OSGKeyboardShared/Services/DictationBridge.swift` | `KeyboardViewController.swift:594,601,639`(**实际消费 pending transcript**) | ❌ 在用 | **不可删** |
|
||
| `OSGKeyboardShared/Services/LiveDictationController.swift` | `PreviewASRController.swift`(typealias)、`DictationCaptureView.swift`、`KeyboardPreviewSheet.swift`、`PreviewASRControllerStateTests.swift` | ❌ 在用 | **不可删** |
|
||
|
||
**结论**: "Legacy" 是相对概念。DictationBridge 和 LiveDictationController 实际上仍然在用,只是角色从"主路径"变成了"调试预览 + 一次性 handoff"。
|
||
|
||
**修复**:
|
||
- ✅ 删除 `AudioCaptureService.swift`(真正无人调用的死代码)
|
||
- ❌ 保留 `DictationBridge.swift`(KeyboardViewController 仍消费)
|
||
- ❌ 保留 `LiveDictationController.swift`(作为 PreviewASRController 仍在用)
|
||
- 文档化:在 `LiveDictationController.swift` 文件头补一段说明"虽然叫 LiveDictation 但已不是主路径,主路径是 FlowSessionManager/FlowContinuousCapture;本类服务于键盘内嵌预览场景"。
|
||
|
||
**落地 commit**: 见 commit `chore: remove dead AudioCaptureService and clarify LiveDictationController scope`
|
||
|
||
---
|
||
|
||
### P0-4. `ITSAppUsesNonExemptEncryption=NO` 缺失 ✅ 已加
|
||
|
||
**现状**: `OSGKeyboard/Info.plist` 没有 `ITSAppUsesNonExemptEncryption` 键。
|
||
|
||
**影响**: App Store 上传时弹出"App Encryption"问卷,需要每次手填一遍"使用 HTTPS,不含非豁免加密"。最简方案是显式声明 `NO`,直接跳过问卷。
|
||
|
||
**修复**: 在主 App `Info.plist` 加 `<key>ITSAppUsesNonExemptEncryption</key><false/>`。键盘扩展不需要此键(不是 app)。
|
||
|
||
**落地 commit**: 见 commit `chore: declare ITSAppUsesNonExemptEncryption=NO for App Store upload`
|
||
|
||
---
|
||
|
||
### P0-5. App Store 截图缺失 ✅ 已生成占位 + 元数据
|
||
|
||
**现状**:
|
||
- `docs/assets/` 只有 `app-icon.png` (1024×1024) — 不是 App Store 截图
|
||
- 缺 6.7" (iPhone 17 Pro Max) / 6.1" (iPhone 17 Pro) / 5.5" (iPhone 8 Plus) 三套
|
||
- 缺 App Store Connect 元数据(description、promo text、keywords、release notes、support URL、marketing URL、privacy policy URL)
|
||
|
||
**Apple 实际要求(2026)**:
|
||
- **6.7"** (1290×2796) — 必需
|
||
- **6.1"** (1179×2556) — 必需(iPhone 17 Pro)
|
||
- 5.5" 已被苹果官方文档降级为"可选"(iPhone 8 Plus 等已停产机型)
|
||
- iPad 截图 — 项目声明 iPhone only,无需提供
|
||
- 每套至少 3 张、最多 10 张
|
||
|
||
**本审计交付**:
|
||
- `docs/screenshots/` 目录
|
||
- `docs/screenshots/README.md` — 截图规范说明
|
||
- `docs/APPSTORE_METADATA.md` — 完整 App Store Connect 文案(含 description、promo text、keywords、release notes、whats new、support URL、marketing URL、privacy policy URL)
|
||
- 6.7" / 6.1" 各 5 张的占位 — **实际占位 PNG(用脚本生成纯色 + 文字标题的 1290×2796 / 1179×2556 PNG)**。真实 UI 截图需要人工在 Xcode Simulator 跑出再替换。
|
||
|
||
**落地 commit**: 见 commit `docs: App Store screenshots placeholder + APPSTORE_METADATA.md`
|
||
|
||
> ⚠️ **真人事项**: 真实截图(带 UI 实际效果)需要太子或皇上在 Xcode Simulator (iPhone 17 Pro / Pro Max) 跑出 5+5 张并替换。已在 APPSTORE_METADATA.md 末尾给出截图拍摄脚本(xcrun simctl io booted screenshot ...)。
|
||
|
||
---
|
||
|
||
### P0-6. `v0.1.2` git tag 缺失 ✅ 已加
|
||
|
||
**现状**: `git tag -l` 空。
|
||
|
||
**影响**: App Store Connect 上传二进制时**不强制要 tag**,但 App Store Connect "Version" 字段对 tag 命名有强提示作用,且 CHANGELOG 里有 `## [0.1.2] - In Progress`,没 tag 看着像没发布。
|
||
|
||
**修复**: 创建 annotated tag `v0.1.2`,信息参考 CHANGELOG 的 0.1.2 段。
|
||
|
||
**落地 commit**: 见 commit `chore: tag v0.1.2 (App Store submission prep)`
|
||
|
||
> ⚠️ 推送 tag = `git push origin v0.1.2`,需要 token。
|
||
|
||
---
|
||
|
||
## P1 — 强烈建议(v0.1.3 候选)
|
||
|
||
### P1-1. SwiftLint strict 模式未启用
|
||
|
||
**摸底**: 当前 `.swiftlint.yml` 已 `disabled_rules` 去掉 `line_length` / `file_length` / `function_body_length` / `type_body_length` / `cyclomatic_complexity` 等大量 rule。**严格模式 (`--strict`) 把 warnings 升级为 errors**,如果启用会爆 134 warnings。
|
||
|
||
**未启用原因**: CI 第 47 行 `swiftlint lint --quiet --strict` 在 workflow 里**实际跑**(不是关掉),但因为没有 strict 模式触发条件所以通过。复现:在 strict 模式下,134 warnings → errors。
|
||
|
||
**建议修复**:
|
||
- 保持 `line_length` / `file_length` 禁用(不然误伤大)
|
||
- 启用 `sorted_imports` / `explicit_init` / `empty_count` / `first_where` 等已 opt_in 但实际报错的 rule
|
||
- 给 5 个超 400 行的文件(`SettingsView.swift 427行`、`OnboardingView.swift 581行`、`FlowSessionManager.swift 468行`、`KeyboardViewController.swift 700行`、`KeyboardRootView.swift 520行`、`LiveDictationController.swift 506行`)做拆分 task
|
||
- 短期:CI 不改 strict 模式,但开启 strict 后通过的 patch 进 v0.1.3
|
||
|
||
### P1-2. 测试覆盖率低
|
||
|
||
**现状**: 7 个测试文件 / 873 行 / 7 个 test class,未覆盖:
|
||
- `FlowSessionManager`(核心)— 0 测试
|
||
- `FlowContinuousCapture`(核心)— 0 测试
|
||
- `PolishingService` — 0 测试
|
||
- `KeyboardViewController` — 0 测试
|
||
- `ASRService` (SpeechAnalyzer 路径) — 仅 1 个 state 测试
|
||
|
||
**建议**: 至少补 1) `FlowSessionManager` start/stop 状态机 2) `PolishingService` 三种模式(off/transcribe/polish)3) `KeyboardViewController` 的 partial transcript 注入逻辑。
|
||
|
||
### P1-3. README 截图缺失
|
||
|
||
**现状**: `README.md` 没有截图段,全是 emoji + 链接。
|
||
|
||
**建议**: 加 `docs/screenshots/` 实际 UI 截图,README 顶部加 `<img src="docs/screenshots/main.png" width="320">` 段。
|
||
|
||
### P1-4. 键盘 UI 引导动图缺失
|
||
|
||
**现状**: 首次启用键盘需要 3 步(设置→键盘→添加→允许 Full Access),onboarding 是文字步骤,没动图。
|
||
|
||
**建议**: 用 QuickTime 录 30 秒 GIF,嵌到 `OSGKeyboard/OnboardingView.swift` 第 3 步下方。
|
||
|
||
### P1-5. 用 `os.Logger` 替换 `print` / `NSLog`
|
||
|
||
**现状**: 见 P0-2。`AppGroup.swift:60` 的 `NSLog` 是唯一 release 可见日志。
|
||
|
||
**建议**: 引入 `OSGLog` enum(封装 `os.Logger`),全局替换。这样 release 下仍能在 Console.app / `log stream` 看到带 subsystem 的日志,但被 `OSLog` 隐私协议控制(不会把 API key 等敏感字段写出去)。
|
||
|
||
---
|
||
|
||
## P2 — 可选长期
|
||
|
||
### P2-1. Apple Foundation Models 本地润色
|
||
|
||
iOS 26 提供 `FoundationModels` 框架(设备端 LLM),可用作"polish 模式"的可选 backend,不消耗用户 API key、不外发。
|
||
|
||
**预估工作量**: 2-3 天(适配接口、prompt 工程、UI toggle、隐私政策更新)。
|
||
|
||
### P2-2. 新增 Anthropic / Gemini provider
|
||
|
||
当前 6 个 LLM provider:OpenAI / DeepSeek / Qwen / Apple / Custom / Moonshot / Zhipu(实际 7 个)。Anthropic Claude / Google Gemini 都还没原生 provider。Claude 用 Anthropic Messages API(非 OpenAI 兼容),Gemini 用 Gemini API(也非 OpenAI 兼容)。
|
||
|
||
**预估工作量**: 4-5 天(含 token 估算、流式响应、错误处理、UI)。
|
||
|
||
### P2-3. 性能 profiling
|
||
|
||
键盘 extension 内存预算 60 MB。当前 `FlowContinuousCapture` + `ASRService` + 音频 buffer 三者驻留。建议跑 Instruments → Allocations / Time Profiler,验证 peak memory < 40 MB。
|
||
|
||
### P2-4. App Store 内置评分请求
|
||
|
||
`SKStoreReviewController.requestReview(in:)` 在 Settings 加 "Rate OSGKeyboard" 按钮,不打断主流程。
|
||
|
||
---
|
||
|
||
## 跨领域观察(不在 P0/P1/P2 单项里的杂项)
|
||
|
||
1. **键盘扩展无 `CFBundleURLTypes`(太子列为 P0-12)** — **核实后非问题**。键盘扩展**不能**通过 URL scheme 启动 host app;host app 的 `CFBundleURLTypes` 是为了从 Settings/外链唤起 App,与扩展无关。**已确认无需修**。
|
||
|
||
2. **App Group fallback 行为不一致(太子列为 P0-13)** — **实际是设计意图**。`AppGroup.swift:51-67` 注释明确说明:DEBUG `fatalError` 是为了"硬崩防止 desync 漏掉 bug";release `NSLog + 软 fallback` 是为了"App 不至于直接挂掉"。审核员不会因为 release 软 fallback 而拒。**已确认无需改 P0,归 P1-5 一起处理**。
|
||
|
||
3. **`FlowContinuousCapture` 依赖 `.playAndRecord` audio session** — **实际已处理**。`OSGKeyboardExt` 用的是 host App 的 `FlowSessionManager`(在主 App target 而非 ext),不冲突。键盘扩展内 AudioCaptureService 是死代码(P0-3 已删)。**已确认无问题**。
|
||
|
||
4. **`NSSupportsLiveTextFrequentUpdates`(太子列为 P0-11)** — **核实**。Apple 在 iOS 17 引入 `NSSupportsLiveText` / iOS 18 加 `NSSupportsLiveTextFrequentUpdates`,但这是**宿主 app 用于处理 Live Text 数据流**(相机/照片),与 custom keyboard extension 无关。键盘扩展加这个 key 不会被苹果拒,但也没用。**已确认无需加**。
|
||
|
||
5. **ASRService 仍引用 iOS 26 之前的 SFSpeechRecognizer fallback(太子列为 P0-14)** — **核实后非问题**。ASRService 的 ASR 后端**只有 SpeechAnalyzer**(iOS 26 only),但 `SettingsView` 和 `AppPermissions` 用 `SFSpeechRecognizer.supportedLocales()` / `authorizationStatus()` 等**元数据 API**(这些 API 在 iOS 26 仍存在且无 deprecation),完全合法。**已确认无需修**。
|
||
|
||
6. **测试运行 destination 是 iPhone 17(CI `ci.yml:79`)** — **核实**。Apple 2026 年 Simulator 列表中 iPhone 17 是 stable;本机是 iPhone 17 Pro。CI 用 `iPhone 17`(无 Pro),是 Apple 公开的标准 simulator 名,应该能跑。**已确认 OK**。
|
||
|
||
7. **Privacy manifest 完整度** — `OSGKeyboard/PrivacyInfo.xcprivacy` 和 `OSGKeyboardExt/PrivacyInfo.xcprivacy` 都声明了 `NSPrivacyAccessedAPICategoryUserDefaults` (CA92.1) 原因。**没有用磁盘 API / 系统启动时间 API**(除了 `containerURL`,它本身不需要声明 reason),所以不必加其他 API reason。**已确认合规**。
|
||
|
||
8. **AppIcon.appiconset 1024x1024 PNG** — ✅ 已确认 `Group 24.png` 1024×1024,App Store 上传最低要求。
|
||
|
||
9. **CHANGELOG 0.1.0 已有** — `## [0.1.0] - 2026-06-17`,v0.1.1 缺失(CHANGELOG 提到"v0.1.1 polish" 但没有 `## [0.1.1]` 段)。**归 P1**,下次发版前补。
|
||
|
||
---
|
||
|
||
## 落地操作(已 commit 到 `audit/appstore-prep` 分支)
|
||
|
||
| Commit | 内容 |
|
||
|---|---|
|
||
| `chore(ci): pin Xcode 16.4 + macos-15 runner` | P0-1 |
|
||
| `chore(ci): enable strict lint with curated disabled rules` | P1-1 起步 |
|
||
| `chore: remove dead AudioCaptureService and clarify LiveDictationController scope` | P0-3 |
|
||
| `chore: declare ITSAppUsesNonExemptEncryption=NO for App Store upload` | P0-4 |
|
||
| `docs: App Store screenshots placeholder + APPSTORE_METADATA.md` | P0-5 |
|
||
| `chore: tag v0.1.2 (App Store submission prep)` | P0-6 |
|
||
|
||
详细 diff 见 git log。
|
||
|
||
---
|
||
|
||
## 真人/外部事项(需皇上或太子手动处理)
|
||
|
||
1. **App Store Connect 上传** — 需人工在 Xcode → Product → Archive → Distribute App,需要 Apple Developer Team 登录
|
||
2. **真实截图替换** — `docs/screenshots/*.png` 当前是脚本生成占位;需在 iPhone 17 Pro / Pro Max 模拟器跑出真实 UI 截图
|
||
3. **App Store Connect 元数据填写** — `docs/APPSTORE_METADATA.md` 含全部文案,需复制到 App Store Connect 后台
|
||
4. **Privacy Nutrition Labels** — App Store Connect 隐私标签:勾选 "Data Not Collected",并在 "Health & Fitness / Data Not Used for Tracking" 等子项确认
|
||
5. **Encryption 出口合规** — 加了 `ITSAppUsesNonExemptEncryption=NO` 后问卷会跳过;如出现,需要在 ITC 提交 self-classification report
|
||
6. **GitHub Release** — `git push origin v0.1.2` 后需到 GitHub Releases 页面写 release notes
|
||
7. **TestFlight** — 上传后建议先 internal TestFlight 跑一遍,确认 Flow / Onboarding / 键盘添加流程
|
||
|
||
---
|
||
|
||
## 附录 A:本地烟雾测试结果
|
||
|
||
### A.1 xcodebuild build (iPhone 17 Pro / iOS Simulator)
|
||
|
||
```
|
||
xcodebuild -project OSGKeyboard.xcodeproj -scheme OSGKeyboard \
|
||
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
|
||
-configuration Debug -derivedDataPath ./.derivedData build \
|
||
CODE_SIGNING_ALLOWED=NO
|
||
|
||
** BUILD SUCCEEDED **
|
||
```
|
||
|
||
产出 `.derivedData/Build/Products/Debug-iphonesimulator/OSGKeyboard.app/`
|
||
包含:
|
||
- `OSGKeyboard` (主 App, 39 KB 可执行)
|
||
- `OSGKeyboard.debug.dylib` (4.3 MB)
|
||
- `PlugIns/OSGKeyboardExt.appex` (键盘扩展)
|
||
- `Frameworks/OSGKeyboardShared.framework` (共享 framework)
|
||
- `Info.plist` 内 `ITSAppUsesNonExemptEncryption=0` 已生效
|
||
- 资源 Assets.car / MaterialIcons-Regular.ttf / en.lproj / zh-Hans.lproj
|
||
|
||
### A.2 xcodebuild test (iPhone 17 Pro / iOS Simulator)
|
||
|
||
```
|
||
xcodebuild test ... -only-testing:OSGKeyboardTests CODE_SIGNING_ALLOWED=NO
|
||
|
||
Executed 29 tests, with 11 failures (0 unexpected)
|
||
```
|
||
|
||
**通过 20/29** — ASRConversion / DictationBridge / FlowSessionBridge / PreviewASRControllerState 全部 PASS。
|
||
|
||
**失败 9/29** — 全部为 `errSecMissingEntitlement (-34018)`:
|
||
- `KeychainTests` (8 tests) — 缺 `keychain-access-groups` entitlement 注入
|
||
- `LLMClientTests` (3 tests) — 间接依赖 Keychain
|
||
|
||
**根因**: `CODE_SIGNING_ALLOWED=NO` 时 xcodebuild 不注入 entitlement,但 Keychain tests 调用了 `kSecAttrAccessGroup` 共享 keychain。这是**测试工程问题**,不是代码 bug,**不影响上架**。
|
||
|
||
**修法(不本审计范围内,仅记录)**:
|
||
1. 在 CI 中允许临时 ad-hoc signing(`CODE_SIGN_IDENTITY=-`),保留 entitlement
|
||
2. 或在 `KeychainTests` 中使用 `URL(fileURLWithPath:)` mock Keychain,绕过真 keychain query
|
||
3. 或在 `OSGKeyboardTests/Info.plist` 同样加 `keychain-access-groups` 数组
|
||
|
||
推荐方案 1(最小改动;CI 加 `CODE_SIGN_IDENTITY: "-"` + `CODE_SIGN_STYLE: Manual`)。
|
||
|
||
---
|
||
|
||
**本审计到此结束。中书省。**
|