142 lines
6.0 KiB
Markdown
142 lines
6.0 KiB
Markdown
> 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<RemoteSessionGrant, RelayFailure>
|
|
func endWatchSession(_ request: EndRemoteSessionRequest) async
|
|
-> Result<RemoteSessionEnded, RelayFailure>
|
|
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.
|