Summary
Make the V2 durable session log and changes feed deterministic, scalable, and reconnect-safe.
Source discussion: https://discord.com/channels/1391832426048651334/1522263729318658181
Related PR discussed: #34962
Decisions / direction
- Rename
log.caught_up to a single-word marker, preferred: log.synced.
- Marker means “synced through this cursor/watermark,” not “the system is idle.”
- Do not implement historical replay as one giant
.all() read.
- Use a fixed replay watermark, page up to that target, emit
log.synced, then tail new events.
- Snapshot endpoints should expose watermarks so clients can compose fetch + tail without a race.
event.changes() is a dirty hint channel, not the source of truth. Correctness comes from session.log(after).
Sketch
const target = await latestSeq(sessionID)
while (cursor < target) {
const batch = await readPage({ after: cursor, through: target, limit })
yield* batch
cursor = batch.at(-1)?.seq ?? target
}
yield { type: "log.synced", sessionID, seq: target }
Open questions
- Marker field name:
sessionID vs aggregateID.
- Internal page size and backpressure behavior.
- Whether full replication needs a generalized aggregate log/list API beyond session logs.
Summary
Make the V2 durable session log and changes feed deterministic, scalable, and reconnect-safe.
Source discussion: https://discord.com/channels/1391832426048651334/1522263729318658181
Related PR discussed: #34962
Decisions / direction
log.caught_upto a single-word marker, preferred:log.synced..all()read.log.synced, then tail new events.event.changes()is a dirty hint channel, not the source of truth. Correctness comes fromsession.log(after).Sketch
Open questions
sessionIDvsaggregateID.