8.8 KiB
8.8 KiB
ios-completion — 收尾波:6 项整改 + P2 全波 + Android 令牌对齐
编排 doc。orchestrator 冻结的跨 agent 契约在 §1,任何 builder 不得偏离; 任务/Owns 表在 §2;验收在 §3。进度记录仍归
docs/PROGRESS_LOG.md(orchestrator 独写)。
审计依据:2026-07-29 的 16-agent iOS 完成度审计(43 计划任务 27 DONE / 11 PARTIAL / 5 MISSING)。
用户已定的三个范围问题:
- P2 全 5 项(T-iOS-31…35)在范围内 —— 计划 §7 要求"未按 P0 规格扩写 RED 清单前不得分派", 故每个 P2 builder 的第一步就是扩写自己那一项的 RED 测试清单(写进本 doc §4 追加区),再进入 GREEN。
- Apple 账号 = 免费个人 team(Team ID
C738Z66SRW,O=Yaojia Wang)。 ⇒ Push Notifications capability 不可用:aps-environmententitlement 若默认挂上,真机构建会直接失败。 entitlements 必须做成默认不挂、env 开关(见 A1)。 - Android 的
WEBTERM_TOKEN支持一起补(与 iOS 侧文件完全不相交,可并行)。
1. 冻结契约(FROZEN — orchestrator 已核对服务端源码,builder 只许实现不许改)
1.1 访问令牌(WEBTERM_TOKEN)—— 原生客户端接入方式
服务端事实(src/http/auth.ts、src/server.ts:340-430,1353-1385):
| 项 | 值 | 出处 |
|---|---|---|
| Cookie 名 | webterm_auth |
auth.ts:30 AUTH_COOKIE_NAME |
| Cookie TTL | 2592000 秒(30 天) | auth.ts:34 |
| 登录端点 | POST /auth |
server.ts:393 |
| 请求体 | {"token":"<t>"},Content-Type: application/json |
server.ts:396,408-409 |
Accept 头 |
必须不含 text/html |
server.ts:366-369,410 —— 含 text/html 会被当成表单走 302 重定向,而不是 204/401 |
| 成功 | 204,带 Set-Cookie: webterm_auth=… |
server.ts:412-414 |
| 令牌错误 | 401 + {"error":"invalid token"} |
server.ts:418 |
| 限流 | 429,10 次/分钟/IP | server.ts:100,399-402 |
| 服务端未启用鉴权 | 204 但没有 Set-Cookie |
server.ts:404-407 |
| 鉴权后 | 每个 HTTP 请求 + WS upgrade 都要带 Cookie: webterm_auth=<t> |
server.ts:1375 |
| 令牌字符集 | [A-Za-z0-9._~+/=-],长度 16–512 |
CLAUDE.md / config 校验 |
实现决定(冻结):
- 原生客户端自己知道令牌,因此 直接手写
Cookie: webterm_auth=<t>请求头即可, 不解析Set-Cookie、不依赖系统 cookie 存储。理由:URLSession/OkHttp 的 cookie jar 在 WS upgrade 上的行为不一致且难测;手写头与既有Origin手写头是同一个模式(Endpoints.swift:81), 可被纯函数单测钉死。 POST /auth只用作配对期的一次性校验探针:- 204 有
Set-Cookie⇒ 令牌正确,保存; - 204 无
Set-Cookie⇒ 该服务器没开鉴权,令牌不必保存(不得据此认为"已认证"); - 401 ⇒ 令牌错,UI 报"令牌不正确";429 ⇒ UI 报"尝试过多,稍后再试"。
- 204 有
Cookie头与Origin头正交:令牌不替代 Origin 检查,两者都要带(server.ts:1363-1379是先 Origin 再 cookie)。- 令牌是密级材料:存 Keychain / Android Keystore-backed 存储,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly、禁kSecAttrSynchronizable(沿用SecItemShim.swift既有约定); 绝不写日志、绝不进 URL query、绝不进崩溃报告。 - 401 语义:任何 RO/G 请求收到 401 ⇒ 抛类型化
.unauthorized(不是通用网络错), UI 引导去补令牌。WS upgrade 收 401 ⇒ 终态,不进退避环(与.replayTooLarge同级处理)。
1.2 git 面板端点(照 Android 已实现的形状;服务端为唯一真源)
Origin-iff-G 规则(Endpoints.swift:81)照旧:写操作必带 Origin,只读绝不带。
| 端点 | 方法 | R/W | Android 参考实现 |
|---|---|---|---|
/projects/log |
GET | RO | api-client/.../models/GitLog.kt |
/projects/pr |
GET | RO | 同上 |
/projects/worktree/state |
GET | RO | — |
/projects/git/stage |
POST | G | models/GitWrite.kt |
/projects/git/commit |
POST | G | 同上 |
/projects/git/push |
POST | G | 同上 |
/projects/git/fetch |
POST | G | — |
/projects/worktree |
POST | G | routes/GitRouteShapeTest.kt |
/projects/worktree/prune |
POST | G | 同上 |
/sessions |
GET | RO | —(claude --resume 历史,T-iOS-32) |
/live-sessions/:id/queue |
POST | G | —(w2 pty 注入队列) |
真源是 src/ 的实现(src/http/projects.ts / src/server.ts),Android 只作交叉校验;
两者若不一致,以 src/ 为准并在返回条目里报告该不一致。
1.3 免费 team 的签名与 entitlements(冻结)
DEVELOPMENT_TEAM = C738Z66SRW。CODE_SIGN_IDENTITY的历史值"iPhone Developer"是已废弃串,改"Apple Development"(或删掉让 Automatic 自己选)。WebTerm.entitlements(含aps-environment)默认不挂。挂载由 env 开关控制, 未设时CODE_SIGN_ENTITLEMENTS必须为空 —— 免费 team 挂上就真机构建失败。 开关名冻结为WEBTERM_PUSH_ENTITLEMENTS(值=entitlements 相对路径),并写进ios/README.md。UIBackgroundModes: [remote-notification]可以无条件加(背景模式不是 capability,免费 team 不受限)。
2. 任务表(Owns 铁律:不得编辑本任务 Owns 之外的文件)
Wave A(串行,1 agent)—— 全员依赖的工程根
| ID | 任务 | Owns |
|---|---|---|
| A1 | 签名解锁 + Info.plist 全量键 + 可选 entitlements | ios/project.yml、ios/App/WebTerm/WebTerm.entitlements(新) |
A1 必须一次把后续所有波次需要的 Info.plist 键都加好(它是 project.yml 的唯一 owner):
NSMicrophoneUsageDescription、NSSpeechRecognitionUsageDescription(T-iOS-31)、
UIBackgroundModes: [remote-notification](T-iOS-21)。
Wave B(并行 5 agent,目录互斥)—— 包层 + CI + Android
| ID | 任务 | Owns |
|---|---|---|
| B1 | APIClient 全部新端点(§1.1 /auth + §1.2 全表)+ 模型 + 测试,覆盖率≥80% |
ios/Packages/APIClient/** |
| B2 | HostRegistry:按主机存访问令牌(Keychain,§1.1 存储约定)+ 迁移安全 | ios/Packages/HostRegistry/** |
| B3 | SessionCore:WS upgrade 带 Cookie、401→终态 .unauthorized、不进退避环 |
ios/Packages/SessionCore/** |
| B4 | ClientTLS 覆盖率 55.76%→≥80% + 纳入覆盖率门 + 修 CI 三条腿 | ios/Packages/ClientTLS/Tests/**、ios/IntegrationTests/scripts/coverage-gate.sh、.github/workflows/ios.yml |
| B5 | Android WEBTERM_TOKEN 支持(§1.1 同一契约,OkHttp 侧) |
android/** |
Wave C(串行 4 agent)—— App 层
串行原因:新增 .swift 文件必须 xcodegen generate 重写共享的 .xcodeproj,并行会互相踩;
串行后无文件归属冲突,每个 agent 可自由跑 xcodegen + xcodebuild 全量验证。
| ID | 任务 | 内容 |
|---|---|---|
| C1 | 令牌 UI + 传输接线;移除主机 UI(顺带修 PushRegistrar.handleHostRemoved 死钩子) |
|
| C2 | git 面板 UI + worktree 表(含 T-iOS-32)+ claude --resume 历史 |
|
| C3 | 终端内搜索(T-iOS-33)+ 语音 PTT(T-iOS-31) | |
| C4 | 主题 + Dynamic Type(T-iOS-34)+ web ?join= 互通(T-iOS-35) |
Wave D(并行 3 agent,report-only / 文档)
| ID | 任务 |
|---|---|
| D1 | 安全复核:令牌路径(两端)+ entitlements + 无令牌泄漏(日志/URL/崩溃) |
| D2 | 全量验收(T-iOS-36 + T-iOS-18/19/30 的机器可执行部分):包测/App 测/集成/覆盖率门/模拟器构建/真机构建尝试,只报真实数字 |
| D3 | 文档:README.md、ios/README.md、docs/PLAN_IOS_CLIENT.md、docs/PLAN_IOS_IPAD.md 勾选与过期表述 |
Wave E(串行,条件触发)
| ID | 任务 |
|---|---|
| E1 | 修 D1/D2 报出的 CRITICAL/HIGH(无则跳过) |
3. 验收门(每个 builder 自查,D2 复核)
- TDD:先 RED 再 GREEN(§4 的 RED 清单是 P2 任务的前置交付物)。
swift build+swift test本包全绿;改到 App 层则xcodegen generate+ iPhone 16 Pro 模拟器xcodebuild ... test全绿。- 受门包覆盖率 ≥80%(B4 后 ClientTLS 也进门)。
- 零
TODO/FIXME/占位实现;零硬编码密钥;令牌零日志。 - 诚实报告:跑不了的(真机/付费账号/CI 平台)写 DEFERRED 并给出手工步骤,不得报"通过"。