feat(ios): W1 leaf packages — ReconnectMachine/PingScheduler, GateState/AwayDigest, HostRegistry, APIClient+pairing probe

T-iOS-5: pure reconnect reducer (1s→30s cap, mirrors terminal-session.ts) + 25s PingScheduler, 16 tests
T-iOS-6: gate epoch tracker (rising-edge semantics, canDecide guard) + AwayDigest reducer, 25 tests
T-iOS-7: HostRegistry with SecItemShim keychain seam, 30 tests, 88.1% own-src coverage
T-iOS-8: APIClient (Origin iff-G invariant) + two-step pairing probe, 45 tests, 98.1% coverage
Contract ruling: probe returns Result<HostEndpoint,_> (Host{id,name} built by pairing VM) —
resolves frozen-contract contradiction reported via BLOCKED protocol; adds Tunables.pairingProbeTimeout(10s)
Verified: 178 tests green across 5 packages; coverage gates pass; zero Owns violations
This commit is contained in:
Yaojia Wang
2026-07-04 21:53:41 +02:00
parent 2ab93c9682
commit 95438cdc12
32 changed files with 3772 additions and 4 deletions

View File

@@ -306,6 +306,7 @@ public struct AwayDigest: Sendable, Equatable {
| `telemetryStaleTtlMs` | 30_000 | 镜像 public/tabs.ts:45 `STATUSLINE_TTL_MS`= 服务器默认 src/config.ts:63服务器侧 env 可覆盖——iOS 固化默认值,主机改配置时会漂移,已接受) |
| `digestFadeDelay` | 8 s | T-iOS-14 digest 自动淡出 |
| `titleMaxLength` | 256 | T-iOS-23 OSC 标题净化上限 |
| `pairingProbeTimeout` | 10 s | 配对探针整体 deadline§3.4 契约裁定新增;两步探针任一步挂起超此时限 → `.timeout` |
| `maxWSMessageBytes` | 16 MiB`16 * 1024 * 1024` | ≥ 6 × 默认 SCROLLBACK_BYTES(2 MiB) + 帧包络。**耦合警示(写进 doc comment**:回放单帧 ≈ SCROLLBACK_BYTES × JSON 转义系数16×控制字节→`\uXXXX`src/protocol.ts:186SCROLLBACK_BYTES 为服务器 env 可调且客户端运行时不可知——超限 → 不可重试 `.replayTooLarge`§3.2 / T-iOS-9/10 |
### 3.3 `HostRegistry`
@@ -356,7 +357,11 @@ public enum PairingError: Error, Equatable { // [S:hybrid] 错误分类学,逐
case tlsFailure, timeout
}
public func runPairingProbe(endpoint: HostEndpoint, http: any HTTPTransport,
ws: any TermTransport) async -> Result<Host, PairingError>
ws: any TermTransport) async -> Result<HostEndpoint, PairingError>
// 【契约裁定 2026-07-04T-iOS-8 BLOCKED 上报】原冻结签名返回 Result<Host,…>,但 Host 是 §3.3
// HostRegistry 类型,APIClient 依赖边只有 WireProtocol(§1 叶子包零耦合)——签名自相矛盾。
// 裁定:探针职责=验证 endpoint,返回 HostEndpoint;Host{id,name} 由 T-iOS-12 PairingViewModel
// 构造后入 store(id/name 本就非探针所知)。超时经 Tunables.pairingProbeTimeout(§3.2.1 新增行)。
// 探针两步:①GET /live-sessions(无 Origin,验可达+形状) ②WS attach(null)+立即 kill 往返
//(带 Origin,验 isOriginAllowed 精确匹配)。任何失败 → 映射到 PairingError,UI 内联显示。
// 注:扫码来源的 endpoint 必须先经 T-iOS-12 的"确认 host"步——用户未确认前不得调用本探针(探针①就会联网)。
@@ -585,7 +590,7 @@ W5 验收(report-only, 并行)
- [ ] `hookDecision` body 形状 `{sessionId,decision,token}`403 → 显式错误token 过期话术)
- [ ] 探针①失败分支:连接拒绝 → `hostUnreachable`;返回 HTML → `httpOkButNotWebTerminal`
- [ ] 探针②失败分支WS 401 → `originRejected(hint:…)`hint 含 `ALLOWED_ORIGINS=<scheme>://<拨号 host>[:port]`,与 App 连接的 URL 一致——不含任何 ":443 迷信"
- [ ] 探针全通 → `Result.success(Host)`;探针成功路径里 attach(null) 后必发 kill不留孤儿会话
- [ ] 探针全通 → `Result.success(HostEndpoint)`契约裁定Host 由 T-iOS-12 VM 构造,见 §3.4 注);探针成功路径里 attach(null) 后必发 kill不留孤儿会话
- [ ] 超时FakeClock`.timeout`
- **Steps(实现, GREEN)**: [ ] §3.4 签名 [ ] 服务器约束写进 doc comment按端点精确`hookDecision` body ≤ 4 KBsrc/server.ts:503、≤10 次/分/IPsrc/server.ts:72,504-508——P1 增量端点的限额归 T-iOS-38
- **Accept**: `swift test --package-path ios/Packages/APIClient` 全绿;覆盖率 ≥ 80%