fix(sync): close canonical recovery merge blockers

This commit is contained in:
Timo
2026-08-25 22:24:37 +02:00
parent 213a639314
commit 0d23859a9f
14 changed files with 484 additions and 87 deletions
+14 -4
View File
@@ -41,8 +41,10 @@ not. Heartbeats remain observational and do not mutate canonical state.
`ROOM_DATA` materializes the playing position at snapshot creation and advertises
the optional `media-state-v1` capability. A joining/reconnecting client validates
the room/revision, respects Host Control solo mode and Episode Lobby, then sends an
internal `APPLY_CANONICAL_MEDIA_STATE` message to the existing content/video path.
the room/revision and optional privacy-sanitized media title, respects Host Control
solo mode and Episode Lobby, then queues an internal
`APPLY_CANONICAL_MEDIA_STATE` message on the same ordered content path as newer
live commands.
That path reuses frame election, Netflix/Disney page-API seeks, native play/pause,
the 2-second drift tolerance, and programmatic-event suppression. Recovery only
completes after playback state and position verification. Transient failures
@@ -52,10 +54,18 @@ snapshot advances from its local receipt time while waiting for a target, and
the apply creates no action history, notification, command ACK, or relay media
event.
Current clients announce `media-state-v1` as an optional client capability and
continue sending accepted media controls while alone on a capable relay. If a
room instead falls back to one legacy client that suppresses solo controls, the
relay clears canonical state so a future joiner receives no snapshot rather than
known-unreliable playback truth.
Force Sync remains a two-phase ACK protocol. A valid `PREPARE` is temporary
room-wide choreography; the next authorized `EXECUTE` commits the latest target
visible to peers to canonical state before the relay target TTL. That TTL is
longer than the client ACK timeout so its scheduled fallback can still land. The
visible to peers to canonical state. Delayed execution is logged but remains
valid until newer accepted playback or lobby state explicitly supersedes it; an
untracked post-restart execute retains legacy relay liveness without inventing a
canonical target. The
offline queue replays an adjacent `PREPARE`/`EXECUTE` pair in one paced batch and
retains both if delivery fails. Per-sender
`seq`, peer heartbeats, and the reconnect queue remain separate mechanisms. The
+20 -12
View File
@@ -54,7 +54,7 @@ Payload:
"password": "string, max 128, optional",
"tabTitle": "string, max 100, optional",
"mediaTitle": "string, max 100, optional",
"clientCapabilities": ["chat-v1"],
"clientCapabilities": ["chat-v1", "media-state-v1"],
"protocolVersion": "string, max 16"
}
```
@@ -110,8 +110,9 @@ The internal `currentTime` is the media position at server-owned `updatedAt`.
Playing state advances lazily when a snapshot is requested; paused state stays
fixed. `revision`, `updatedAt`, and `updatedBy` are server-owned. Clients cannot
spoof them. The wire snapshot contains the already-projected `currentTime`,
`revision`, `playbackState`, and `updatedBy`, so clients never compare client and
server wall clocks.
`revision`, `playbackState`, `updatedBy`, and an optional privacy-sanitized
`mediaTitle`, so clients never compare client and server wall clocks and retain
the existing cross-episode guard during recovery.
Only accepted, sanitized room controls update canonical state:
@@ -121,11 +122,12 @@ Only accepted, sanitized room controls update canonical state:
established playback state.
- a valid `force_sync_prepare` records only temporary coordination state. The
next authorized `force_sync_execute` commits the latest room-wide prepared
target as playing before `FORCE_SYNC_TARGET_TTL` expires. This relay TTL is
intentionally longer than the client's `FORCE_SYNC_TIMEOUT` ACK wait so the
normal timeout fallback remains deliverable. Expired targets are cleared and
cannot alter canonical state. The latest valid prepare is also the only
post-demotion execute exemption in Host Control mode.
target as playing. A target older than `FORCE_SYNC_TARGET_DELAY_WARNING` is
logged but remains executable until newer room playback supersedes it, because
receivers are already paused. An execute without retained target still uses
the legacy wire fallback after relay restart but cannot invent canonical state.
The latest valid prepare is also the only post-demotion or reconnect execute
exemption in Host Control mode.
`peer_status` heartbeats are observations and never rewrite canonical intent.
For current clients, the relay drops invalid, duplicate, or regressing `seq`
@@ -152,7 +154,12 @@ relaying the established event names, payloads, and order unchanged. This makes
server-first rollout safe: old clients populate recovery state without needing to
understand or acknowledge it, and new clients consume it only when the relay
advertises the capability. No protocol-version or minimum-version bump is
required. Offline `play`/`pause`/`seek` compaction remains the separate
required. New clients also announce `"media-state-v1"` in optional
`join_room.clientCapabilities`; this only tells a capable relay that the client
keeps canonical state current while alone. When a room falls back to one legacy
client, the relay clears potentially stale canonical state instead of recovering
future joiners to unverified solo playback. Offline `play`/`pause`/`seek`
compaction remains the separate
client-owned layer described below rather than part of the relay capability.
### Offline media intent
@@ -347,9 +354,10 @@ The current extension sends sequence/action metadata but no target; the relay us
the latest validated room target retained from `force_sync_prepare`. In
`host-only` mode, only controllers may send it.
The relay also allows that latest valid initiator's execute event after their
controller state changed before execute. Invalid prepares are dropped and grant
no exemption. The retained target expires after `FORCE_SYNC_TARGET_TTL`, which
includes a relay grace period beyond the client's ACK timeout.
controller state changed or their socket reconnected before execute. Invalid
prepares are dropped and grant no exemption. Newer accepted playback/lobby state
explicitly supersedes the prepared target, so its delayed execute is dropped.
Otherwise, even a delayed execute is relayed to release paused receivers.
## Episode Lobby