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
30 lines
1.1 KiB
Swift
30 lines
1.1 KiB
Swift
import Foundation
|
|
|
|
/// Frozen contract (plan §3.3). Implementations: `KeychainHostStore` (real)
|
|
/// and `InMemoryHostStore` (in-Sources double for this package's tests and
|
|
/// the App-layer VM tests). Immutable style: mutations return the NEW
|
|
/// collection instead of mutating shared state in place.
|
|
public protocol HostStore: Sendable {
|
|
func loadAll() async throws -> [Host]
|
|
/// Insert, or replace the host with the same `id` (position preserved).
|
|
/// Returns the new collection.
|
|
func upsert(_ host: Host) async throws -> [Host]
|
|
/// Removing an unknown `id` is an explicit no-op: returns the unchanged
|
|
/// collection, never throws for "not found".
|
|
func remove(id: UUID) async throws -> [Host]
|
|
}
|
|
|
|
// Pure collection transforms shared by all HostStore implementations (DRY);
|
|
// never mutate `self`, always return a fresh array.
|
|
extension [Host] {
|
|
func upserting(_ host: Host) -> [Host] {
|
|
contains(where: { $0.id == host.id })
|
|
? map { $0.id == host.id ? host : $0 }
|
|
: self + [host]
|
|
}
|
|
|
|
func removing(id: UUID) -> [Host] {
|
|
filter { $0.id != id }
|
|
}
|
|
}
|