Первый concrete impl :event-store. ConcurrentLinkedDeque + eviction на
каждом append (amortized O(1) при стабильном размере буфера).
Контракт (по требованию пользователя):
- maxMessages: Int? — nullable, NO default (явный null = unlimited)
- ttl: Duration? — nullable, NO default (явный null = forever)
- оба null → store forever
- любой non-null → соответствующий eviction policy
Eviction:
- evictExpired(): pollFirst пока head.date < (now - ttl)
- evictOverCapacity(): pollFirst пока size > maxMessages
- обе вызываются синхронно на каждом append
Live tail: MutableSharedFlow(capacity=4096, DROP_OLDEST).
Producer никогда не блокируется — slow subscriber получает свежие события,
за полным покрытием — fallback в :message-store-api.
Tests (13):
- append stores all (both null)
- maxMessages cap evicts
- ttl evicts older than threshold
- both policies apply together (cap=2 + ttl=100ms, a/b/c appends)
- events(null) replays buffer then collects live
- events(after) catches up + live
- earliestEventDate (oldest + empty buffer = now)
- conversationEvents/agentEvents фильтры (default impl в :event-store)
- close clears buffer
- negative maxMessages throws at construction
internal helper snapshot() для тестов — production code использует
events()/events(after) для доступа к буферу.
KMP: jvm + linuxX64 + mingwX64.