> Historical agent handoff. The integrated composition and current test status are documented in `docs/FIRST-DEVICE-PROTOTYPE.md` and `docs/VALIDATION.md`. Proposed wiring below has been incorporated or superseded. # WatchBridge integration proposal Status: accepted with integration decisions below. This proposal is retained as design history; `docs/CONTRACTS.md` and RemoteCore/WatchSession.swift are the authoritative final API. The original suggested signatures below are not the implementation contract. ## Integration decisions - Shared DTOs are implemented, including RelayFailure conforming to Error. - RelayJSON centrally enforces millisecond dates and the 4096-byte payload bound. - PhoneCommandCoordinating is an Actor protocol with explicit request values and a local WatchPeerGeneration argument on every operation, plus activation and deactivation callbacks. This resolves stale counterpart/lifecycle events. - The bounded three-key container remains adapter-owned; RelayMessageKind freezes its v1 routing vocabulary. Only A's single actor consumes command admission. - An adjacent local Swift package provides Common/iPhone/Watch source and test targets. Xcode links its platform products instead of duplicating source files directly in the app targets. New B source files require no project regeneration. - B may use a fake injected actor now. A's production conformance and physical device behavior remain later integration gates, not blockers to B's coding. ## Original proposal (historical) ## Shared RemoteCore additions requested Add typed, `Codable`, `Sendable`, `Equatable` control-plane values to `RemoteCore`. Names are suggestions; the wire values and semantics need to be frozen by integration before B implements them. ```swift public struct BeginRemoteSessionRequest: Codable, Sendable, Equatable { public let schemaVersion: Int } public struct RemoteSessionGrant: Codable, Sendable, Equatable { public let schemaVersion: Int public let sessionID: UUID public let televisionID: UUID public let issuedAt: Date public let maximumCommandLifetimeMilliseconds: UInt64 } public struct EndRemoteSessionRequest: Codable, Sendable, Equatable { public let schemaVersion: Int public let sessionID: UUID } public struct RemoteSessionEnded: Codable, Sendable, Equatable { public let schemaVersion: Int public let sessionID: UUID } public struct RelayFailure: Codable, Sendable, Equatable { public enum Code: String, Codable, Sendable { case unsupportedSchema case malformedRequest case noSelectedTelevision case unavailable case sessionInvalidated } public let schemaVersion: Int public let code: Code } ``` `maximumCommandLifetimeMilliseconds` must be positive and no greater than `CommandAdmission.maximumLifetime * 1_000`. `issuedAt` is encoded as JSON milliseconds since Unix epoch. A grant authorizes no command by itself and must be bound by the phone actor to the currently activated paired WCSession and the phone's currently selected TV. The bridge container remains adapter-owned and has exactly three keys: ```text schemaVersion: Int == 1 kind: String payload: Data (maximum 4 KiB) ``` Freeze these v1 `kind` values: `beginSession`, `session`, `endSession`, `ended`, `command`, `receipt`, and `error`. Every request receives one reply container. Unsupported versions, unknown kinds, missing/extra or incorrectly typed keys, oversized payloads, and structural decode failures fail closed with an `error` reply. Semantic command rejection remains solely in the phone command actor. ## Injected phone actor seam requested Integration/A should provide one actor conforming to a narrow protocol such as: ```swift public protocol PhoneCommandCoordinating: Sendable { func beginWatchSession() async -> Result func endWatchSession(_ request: EndRemoteSessionRequest) async -> Result func submitWatchCommand(_ envelope: CommandEnvelope) async -> CommandReceipt func watchBridgeDidDeactivate() async } ``` That actor remains the only owner of the authorized session and `CommandAdmission`. `WatchBridge` will retain only the injected protocol reference on iPhone. It will not instantiate `CommandAdmission`, perform semantic admission, select a TV, or issue session UUIDs. Please confirm whether a changed paired-watch identity can be surfaced to the actor as `watchBridgeDidDeactivate()` or needs an explicit opaque peer generation parameter. No persistent hardware identifier should cross this seam. ## Project/test wiring requested Update the integrator-owned project generator and generated project to: - compile `Adapters/WatchBridge/Sources/Common` into both app targets; - compile `Sources/iPhone` only into the iPhone target and `Sources/Watch` only into the watchOS target; - link `WatchConnectivity` through normal SDK imports (no third-party runtime); - add adjacent fake-driven test targets for common, iPhone, and watch behavior; - keep the adapter outside the pure algorithm package. The test target must be able to inject a session facade rather than subclassing or manufacturing `WCSession`. Planned coverage: reachable/unreachable delivery, activation/deactivation, phone/watch reinstall or counterpart switch, duplicate callback suppression, reply timeout, lost reply, invalid containers, 4 KiB payload boundary, millisecond date round trips, and application-context sanitization. ## Migration and impact This is additive to contract v1 but introduces the first control-plane wire format. Existing demo apps remain demos until integration explicitly injects the live adapter. Mixed installations fail closed on schema mismatch and expose an update-required/unavailable UI state; they do not fall back to queued commands. No permission string, background mode, credential path, analytics, raw motion, or third-party dependency is requested. Physical iPhone/Watch testing remains required for locked-phone wake, relaunch, counterpart switching, and actual reachability behavior without Xcode attached.