feat(ios): 中继通道 P4/P5——端点抽象、信封、传输、配对与分层错误
- InstanceEndpoint 取代 Instance.origin(旧数据 init(from:) 读成 .direct, 写盘只输出 endpoint;dedupKey 直通用 origin、中继用 relay:<nodeID>) - RelayWire:Swift 侧重算 contracts/test-vectors.json(CanonicalJSON 手写解析器, 因为 Foundation 的解码器会抹掉'拼写',而 intent 摘要定义的正是拼写) - NodeTransport 抽象 + DirectTransport / RelayTransport / NodeTransportRouter; DirectTransport 是薄适配,直通路径行为不变 - RelayTransport:信封构造、签名、加密、验签、解密;响应验签失败即丢弃, 四类以上失败各有独立 UpstreamError 分支与用户文案 - DeviceIdentity(沙盒文件 0600,配对时由节点分配 deviceID)、RelayPairing、配对 sheet - 卡片通道徽标 + freshness 副标题;危险写 intent 在发送瞬间生成 - scripts/export-app-routes.swift 生成 vendored 路由表,AppRoutesContractTests 反向守卫 - 测试 78→92:向量互算、迁移(字面量旧 JSON)、配对四类失败、路由器、 传输往返与各类拒绝路径
This commit is contained in:
@@ -62,6 +62,21 @@ public enum UpstreamRoute: Hashable, Sendable {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the node's agent will demand a confirmation intent for this route.
|
||||
///
|
||||
/// This is the app's copy of `policy.dangerous` in `contracts/agent-policy.json`, and the two
|
||||
/// are asserted equal by the relay repository's policy test. It is a copy rather than a shared
|
||||
/// file because the app cannot read the relay's repository at build time; the test is what
|
||||
/// keeps it from drifting.
|
||||
public var requiresConfirmation: Bool {
|
||||
switch self {
|
||||
case .smsSend, .smsBatchDelete:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum UpstreamError: Error, Equatable {
|
||||
@@ -72,4 +87,46 @@ public enum UpstreamError: Error, Equatable {
|
||||
case timeout
|
||||
case transportFailed
|
||||
case invalidResponse
|
||||
|
||||
// Relay-mode failures. These are deliberately separate cases rather than a reuse of
|
||||
// transportFailed: in relay mode the user has four genuinely different situations to tell
|
||||
// apart, and a single "请求失败" for all of them is what makes a two-hop architecture
|
||||
// impossible to operate.
|
||||
/// The relay could not be reached at all.
|
||||
case relayUnreachable
|
||||
/// The relay took too long to answer.
|
||||
case relayTimeout
|
||||
/// The relay rejected this phone's bearer token: the pairing is no longer valid there.
|
||||
case relayRejectedDevice
|
||||
/// The relay does not know this node, or this phone is not paired to it.
|
||||
case relayNodeUnknown
|
||||
/// The node's agent is not connected to the relay.
|
||||
case agentOffline
|
||||
/// The node is connected but already has the maximum number of requests in flight.
|
||||
case nodeBusy
|
||||
/// The node accepted the request but did not answer within the relay's deadline. It may or may
|
||||
/// not have executed it, which is why the message must not invite a blind retry.
|
||||
case agentTimeout
|
||||
/// The relay refused the envelope before delivering it.
|
||||
case relayBadEnvelope
|
||||
/// The relay is rate limiting this phone.
|
||||
case relayRateLimited
|
||||
/// The node's agent rejected the request: not on its allowlist, or the device is not paired.
|
||||
case relayPolicyDenied
|
||||
/// The node received the request and the confirmation intent did not match.
|
||||
case confirmationRejected
|
||||
/// The envelope was outside the node's freshness window, or its id had already been used.
|
||||
case envelopeExpired
|
||||
/// The node's own rate limit refused a dangerous write.
|
||||
case nodeRateLimited
|
||||
/// The node's agent could not authenticate to its local SimAdmin.
|
||||
case nodeAuthFailed
|
||||
/// The request body exceeded the node's limit.
|
||||
case payloadTooLarge
|
||||
/// The node signed nothing, or the answer was not for this request.
|
||||
case relayResponseUnverifiable
|
||||
/// The relay answered about a different request or node.
|
||||
case relayResponseMismatch
|
||||
/// The node refused the envelope's signature or shape.
|
||||
case envelopeRejected
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user