docs(ios): freeze ios-completion coordination contracts (token/git-panel/signing)

This commit is contained in:
Yaojia Wang
2026-07-30 09:19:31 +02:00
parent a25fe30f1a
commit 0de5557921

View File

@@ -0,0 +1,149 @@
# 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
用户已定的三个范围问题:
1. **P2 全 5 项T-iOS-31…35在范围内** —— 计划 §7 要求"未按 P0 规格扩写 RED 清单前不得分派"
故每个 P2 builder 的**第一步就是扩写自己那一项的 RED 测试清单**(写进本 doc §4 追加区),再进入 GREEN。
2. **Apple 账号 = 免费个人 team**Team ID `C738Z66SRW`O=`Yaojia Wang`)。
**Push Notifications capability 不可用**`aps-environment` entitlement 若默认挂上,真机构建会直接失败。
entitlements 必须做成**默认不挂、env 开关**(见 A1
3. **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._~+/=-]`,长度 16512 | 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 报"尝试过多,稍后再试"。
- **`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** | SessionCoreWS 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 agentreport-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 并给出手工步骤,**不得报"通过"**。
---
## 4. P2 RED 测试清单(由各 P2 builder 在开工第一步追加到本节)
<!-- C3 / C4 在此追加 T-iOS-31/33/34/35 的 RED 清单 -->