diff --git a/event-store/src/commonMain/kotlin/pw/binom/agentik/eventStore/EventStore.kt b/event-store/src/commonMain/kotlin/pw/binom/agentik/eventStore/EventStore.kt index 08948fc..cf55128 100644 --- a/event-store/src/commonMain/kotlin/pw/binom/agentik/eventStore/EventStore.kt +++ b/event-store/src/commonMain/kotlin/pw/binom/agentik/eventStore/EventStore.kt @@ -77,22 +77,33 @@ interface EventStore : AutoCloseable { fun events(after: Instant?): Flow /** - * Date самого старого event'а, всё ещё хранящегося в буфере. + * Date **стартовой точки** буфера. * - * `null` если буфер пуст (или store только что стартовал — ни одного - * события ещё не было). + * - Если буфер не пуст → `date` самого старого буферизованного event'а. + * - Если буфер пуст → текущее время (`Clock.System.now()` на момент вызова). + * + * **Семантика "now если пусто"** важна: позволяет клиенту безопасно + * подписаться на [events](after = earliest) сразу — он получит только + * новые live event'ы, без ложного catchup. Если бы возвращалось + * `Instant.DISTANT_PAST` или `null` (с проверкой), клиент мог бы + * ошибочно подписаться на несуществующий catchup и зависнуть в ожидании. * * **Используется клиентом для gap detection**: * - `lastSeen < earliest` → есть дыра в покрытии, нужен fallback * в message store за диапазоном `[lastSeen, earliest)`. * - `lastSeen >= earliest` → всё доступно через [events](after), * fallback не нужен. - * - `earliest == null` → store пуст, первый live event сам станет - * `earliest` для следующего клиента. + * - `lastSeen == earliest` → OK, первый live event будет > earliest. * - * Suspend потому что в persistent impl'ах требует SQL query (`MIN(date)`). + * **Edge case**: клиент, подключившийся до того как store увидел хоть + * один event, получает `earliest ≈ now`. Его `lastSeen` будет < earliest + * — адаптируется в первом же poll'е и пойдёт через fallback если + * сообщения audit log существуют (для consistency с прошлым). + * + * Suspend потому что в persistent impl'ах требует SQL query (`MIN(date)` + * или `Clock.now()` для пустого буфера). */ - suspend fun earliestEventDate(): Instant? + suspend fun earliestEventDate(): Instant override fun close() }