feat(clients): CLI-15 surface ReplayGap reconnect sentinel as typed signal (4/5 clients)
The gateway emits a ReplayGap sentinel MxEvent at the head of a StreamEvents stream resumed via after_worker_sequence when the requested cursor predates the oldest retained event. Clients previously ignored it, silently mis-treating a lossy resume as continuous. Each client now surfaces the sentinel as a distinct, typed, non-terminal signal (never synthesized, never swallowed) so a consumer can detect the gap and re-snapshot; resume contract is after_worker_sequence = oldest_available_sequence - 1. - .NET: MxEventStreamItem (IsReplayGap/ReplayGap/Event) via new StreamEventItemsAsync + AsStreamItemsAsync extension. Build clean, 87 passed. - Go: EventResult.ReplayGap field + IsReplayGap(); ReplayGap type alias. build/vet/test clean. - Rust: EventItem enum (Event/ReplayGap); EventStream now yields Result<EventItem, Error>; CLI renders REPLAY_GAP line / replayGap JSON. fmt/check/test/clippy clean. - Python: ReplayGap dataclass; stream_events yields pb.MxEvent | ReplayGap. 131 passed. - Shared docs: ClientLibrariesDesign non-goals reframed (reconnect-replay protocol is consumable; auto-reconnect stays a non-goal); CrossLanguageSmokeMatrix resume-gap note. Java client is deferred to the windev batch (no local JRE); CLI-15 stays open until it lands. Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
This commit is contained in:
@@ -63,12 +63,29 @@ Goals:
|
||||
|
||||
Non-goals for v1:
|
||||
|
||||
- client-side reconnectable sessions,
|
||||
- client-side event replay,
|
||||
- automatic client-side session reconnection (the library never transparently
|
||||
re-opens a dropped stream on the consumer's behalf — reconnect timing and
|
||||
policy stay an application concern),
|
||||
- client-side event replay buffering (the gateway owns the replay ring; the
|
||||
client does not retain its own event history),
|
||||
- client-side command batching,
|
||||
- synthetic MXAccess events,
|
||||
- hiding MXAccess handles behind opaque client-only handles.
|
||||
|
||||
The gateway's reconnect-replay *protocol* is, however, consumable from every
|
||||
client: each exposes the `after_worker_sequence` resume cursor on
|
||||
`StreamEvents` and surfaces the gateway's `ReplayGap` sentinel as a distinct,
|
||||
typed, non-terminal signal (CLI-15) so a consumer can detect an evicted-history
|
||||
gap and re-snapshot. Per the no-synthesized-events invariant the client never
|
||||
fabricates or swallows the sentinel — it only makes the gateway's own signal
|
||||
observable. The reaction contract is identical across languages: on a gap,
|
||||
discard cached state, re-snapshot, and resume with
|
||||
`after_worker_sequence = oldest_available_sequence - 1`. The per-language
|
||||
surface follows each idiom (.NET `MxEventStreamItem.IsReplayGap` via
|
||||
`StreamEventItemsAsync`; Go `EventResult.ReplayGap`/`IsReplayGap()`; Rust
|
||||
`EventItem::ReplayGap`; Python yields a typed `ReplayGap` from
|
||||
`stream_events`; Java — pending, tracked with the JDK-17/windows client pass).
|
||||
|
||||
## Public Client Concepts
|
||||
|
||||
All languages should expose the same core concepts, using idiomatic naming:
|
||||
|
||||
Reference in New Issue
Block a user