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:
@@ -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 转义系数(1–6×,控制字节→`\uXXXX`,src/protocol.ts:186);SCROLLBACK_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-04,T-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 KB(src/server.ts:503)、≤10 次/分/IP(src/server.ts:72,504-508)——P1 增量端点的限额归 T-iOS-38
|
||||
- **Accept**: `swift test --package-path ios/Packages/APIClient` 全绿;覆盖率 ≥ 80%
|
||||
|
||||
Reference in New Issue
Block a user