feat(core): deterministic session log replay with synced watermark (#35040)

This commit is contained in:
Kit Langton 2026-07-02 21:38:20 -04:00 committed by GitHub
commit 57e9e9771d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 205 additions and 87 deletions

View file

@ -163,8 +163,9 @@ export interface Interface {
) => Effect.Effect<SessionMessage.Message[], NotFoundError | MessageDecodeError>
/**
* Durable, ordered, gap-free session log read. Replays public durable
* session events after the exclusive `after` cursor, emits a `CaughtUp`
* marker at the replay boundary, then continues live when `follow` is set.
* session events after the exclusive `after` cursor, emits a `Synced`
* marker at the captured replay watermark, then continues live when `follow`
* is set.
* The marker's seq may exceed the last emitted event because non-public
* durable events share the aggregate's sequence space.
*/
@ -172,7 +173,7 @@ export interface Interface {
sessionID: SessionSchema.ID
after?: number
follow?: boolean
}) => Stream.Stream<SessionEvent.DurableEvent | EventLog.CaughtUp, NotFoundError>
}) => Stream.Stream<SessionEvent.DurableEvent | EventLog.Synced, NotFoundError>
/** Latest durable log seq per session. Sessions without events are absent. */
readonly watermarks: (sessionIDs: ReadonlyArray<SessionSchema.ID>) => Effect.Effect<ReadonlyMap<string, EventV2.Seq>>
readonly switchAgent: (input: { sessionID: SessionSchema.ID; agent: string }) => Effect.Effect<void, NotFoundError>
@ -455,8 +456,8 @@ const layer = Layer.effect(
.pipe(Effect.as(events.log({ aggregateID: input.sessionID, after: input.after, follow: input.follow }))),
).pipe(
Stream.filter(
(item): item is SessionEvent.DurableEvent | EventLog.CaughtUp =>
EventV2.isCaughtUp(item) || isDurableSessionEvent(item),
(item): item is SessionEvent.DurableEvent | EventLog.Synced =>
EventV2.isSynced(item) || isDurableSessionEvent(item),
),
),
watermarks: Effect.fn("V2Session.watermarks")(function* (sessionIDs) {