Commit Graph

7 Commits

Author SHA1 Message Date
subochev a05e260457 refactor(event-store): default impls for conversationEvents/agentEvents
ci / JVM build + tests (push) Failing after 1m24s
Методы conversationEvents() и agentEvents() теперь default в интерфейсе:
реализуют фильтрацию через [events] + filterIsInstance.

Зачем:
  - Минимальный контракт для impl — достаточно реализовать только events().
  - InMemoryEventStore и любой новый backend получают работающие
    specialized views автоматически, без копипасты filterIsInstance.
  - Persistent impl'ы (SQL/ksqlite) могут override'нуть для эффективности
    (WHERE conversation_id = ? — не тянуть все events в память), но
    контракт корректен и без override.

Семантика идентична:
  - conversationEvents(after, null)   = events().filterIsInstance<Conversation>()
  - conversationEvents(after, "c-1") = events()...filter { it.conversationId == "c-1" }
  - agentEvents(after)               = events().filterIsInstance<Agent>()

Imports добавлены: kotlinx.coroutines.flow.filter, filterIsInstance.
2026-09-20 15:56:59 +03:00
subochev 7e66baf9e3 feat(event-store): add conversationEvents and agentEvents filters
ci / JVM build + tests (push) Has been cancelled
Расширяет EventStore тремя вариантами подписки (вместо одного events()):

  - events(after)               → Flow<CommonEvent>
      весь поток (микс Agent + Conversation)

  - conversationEvents(after, conversationId?)
      → Flow<CommonEvent.Conversation>
      опциональный фильтр по conversationId (null = все диалоги)

  - agentEvents(after)
      → Flow<CommonEvent.Agent>
      только lifecycle (Created/Deleted/Renamed)

Типизированные subtype'ы вместо Flow<CommonEvent> + .filterIsInstance:
  - compile-time safety на клиенте (нет cast'ов в CommonEvent.Conversation)
  - persistent impl'ы могут делать WHERE conversation_id = ? на уровне БД

Соответствует HTTP-маршрутам в :server:
  - GET /events/all                       ↔ events(after)
  - GET /conversations/{id}/events         ↔ conversationEvents(after, id)
  - GET /events (только agent lifecycle)   ↔ agentEvents(after)

README обновлён — таблица caller→method показывает маппинг.

Совместимость: signals не сломаны (добавление, не изменение).
2026-09-20 15:55:14 +03:00
subochev 1134e32ea2 docs(event-store): README explaining module purpose and contract
ci / JVM build + tests (push) Failing after 1m16s
Документирует:
  - Три принципа дизайна (TTL внутри, catchup+live в одном Flow,
    read-only контракт для observer'ов)
  - Архитектуру двухуровневого хранилища событий со схемой
  - Reconnect pattern с gap detection
  - API EventStore + MutableEventStore (когда какой использовать)
  - Таблица: какой caller принимает какой интерфейс
  - Текущее состояние: interfaces готовы, implementations в работе
  - Зависимости (минимальные: :proto + kotlinx-coroutines)

В том же стиле что и :agent-toolsets/README.md.
2026-09-20 15:51:21 +03:00
subochev e2f0e434d1 refactor(event-store): split into EventStore (read-only) + MutableEventStore
ci / JVM build + tests (push) Failing after 1m49s
Разделяет интерфейс на read-only (EventStore) и write (MutableEventStore).

EventStore (read-only, для consumer'ов):
  - events(after: Instant?): Flow<CommonEvent>
  - earliestEventDate(): Instant
  - close()

MutableEventStore : EventStore (для producer'ов):
  - + append(event: CommonEvent)
  - suspend, не идемпотентный, может быть silently evicted

Зачем:
  - Consumer'ы (server SSE, admin dashboard, parent agents) принимают
    EventStore — compile-time гарантия что они не могут писать в store.
  - Producer'ы (ChatAgent, sub-agents, A2A-bridge) принимают MutableEventStore.
  - Тесты могут использовать EventStore без опасности случайной модификации.

Миграция:
  - :event-store пока без implementations, поэтому ничего не сломалось.
  - Когда добавим InMemoryEventStore — он будет реализовывать оба
    (MutableEventStore = EventStore + append). Подписки получают только
    read-only projection через приведение типа.

Также: импорт обновлён AllEvent → CommonEvent (по rename в :proto).
2026-09-20 15:46:59 +03:00
subochev 1f85cde1b8 feat(event-store): store and emit AllEvent directly
ci / JVM build + tests (push) Failing after 1m17s
Заменяет generic Event envelope (id, date, payload) на типизированный AllEvent
из :proto. EventStore теперь:

  - append(event: AllEvent)
  - events(after: Instant?): Flow<AllEvent>
  - earliestEventDate(): Instant

Изменения:
  - :event-store теперь зависит от :proto (api dependency).
  - Event.kt удалён — AllEvent уже живёт в :proto и несёт date, conversationId,
    typed envelope (Agent или Conversation variant).
  - Generic opaque payload выкинут — typesafety до самого storage.
  - append НЕ идемпотентен (AllEvent не имеет уникального id, два retry
    дадут дубликат). Документировано: для exactly-once использовать catchup
    через :message-store-api (там есть монотонный id).

В KDoc примере reconnect убран лишний null-check у earliestEventDate
(теперь всегда non-null).

Миграция (когда будем интегрировать):
  Producer в :standalone перестаёт делать двойную работу
  (MutableSharedFlow + persistAgentEvent). Вместо этого — один
  eventStore.append(allEvent). Caller'ы SSE будут делать
  store.events(after) → Flow<AllEvent>, без ручной конвертации.

Build green на JVM.
2026-09-20 15:44:17 +03:00
subochev 8bb24dab3c refactor(event-store): earliestEventDate returns non-nullable Instant
ci / JVM build + tests (push) Failing after 1m50s
Раньше возвращал Instant? — null для пустого буфера. Клиенту приходилось
проверять if (earliest != null && ...) — легко ошибиться.

Теперь всегда Instant. Если буфер пуст, возвращает Clock.System.now()
на момент вызова. Это убирает nullable + сохраняет семантически корректное
поведение:

  - Клиент может безопасно сделать store.events(earliest) → получит только
    live event'ы, без ложного catchup.
  - Если бы возвращали null/DISTANT_PAST/null-check, клиент мог бы ошибочно
    подписаться на несуществующий catchup-диапазон.

Edge case (в KDoc): клиент, подключившийся ДО первого event'а, получает
earliest ≈ now. Его lastSeen < earliest → адаптируется в первом poll'е.

Изменение breaking — но :event-store ещё не имеет implementations и
никакие consumer'ы не используют этот метод.
2026-09-20 15:41:30 +03:00
subochev 7358175499 feat(event-store): new :event-store module with interfaces only
ci / JVM build + tests (push) Failing after 1m54s
Выделяет bounded-tail event log в отдельный KMP-модуль. Это **новый
контракт** (не замена :message-store-api/events/EventStore — тот пока жив).

**Двухуровневое хранилище событий**:
  1. :event-store (этот PR) — короткий bounded tail, авто-TTL.
     Для live SSE и recent replay (catchup после короткого disconnect).
  2. :message-store-api (MessageStore) — полный audit log, никогда не
     эвиктится. Source of truth для длинного disconnect / audit query.

**API**:
  - append(Event) — put, идемпотентный по id
  - events(after: Instant?): Flow<Event> — catchup + live в одном Flow
  - earliestEventDate(): Instant? — для gap detection у клиента

**Чего НЕТ в API** (by design):
  - delete/prune методов — TTL/cap eviction полностью на стороне impl.
    Caller'ы не должны забыть вызвать cleanup (single source of truth).
  - conversationId / type в Event — opaque payload, тип envelope'а
    решает producer.

Пока interfaces only — implementations (InMemoryEventStore, persistent)
появятся в следующих коммитах. Никаких изменений в существующем
EventStore в :message-store-api, чтобы не ломать зависимости.

KMP targets: jvm + linuxX64 + mingwX64 (Apple auto-disabled на Linux).

Modules:
  + :event-store — новый, commonMain only, ~150 строк
2026-09-20 15:38:05 +03:00