Files
agentik/sync-core
subochev 92b76c4e3c Добавляет :sync-core — референсная реализация SYNC-SYSTEM.md.
Новый KMP-модуль :sync-core реализует спеку SYNC-SYSTEM.md с нуля, без
опоры на существующие :outbox-api / :journal-api / :client. Цель — собрать
работающую модель «append-only event journal + материализация +
replace-resync», проверить инварианты тестами, и уже потом думать, как это
распределить по существующим абстракциям.

Архитектура (§3-§5 спеки):
  - Cursor(epoch, number) — пара (поколение, счётчик); @JvmInline value
    class; монотонно возрастает внутри эпохи, при wipe/restart эпоха
    меняется и клиент обязан делать replaceState.
  - EventLog / EventLogWriter / EventLogReplica — append-only журнал +
    материализация на стороне клиента (replica ничего не хранит в
    журнале, только state + sync_state + pending).
  - StateStore / Reducer<S, E> — детерминированная свёртка событий в
    снапшот ChatState.
  - PendingEvent + LocalId — offline-write очередь: PENDING → SENT/FAILED.
  - SyncEngine — оркестратор: fetchUpdates → applyRemote → fetchState →
    replaceState → postPending.
  - SyncTransport — четыре endpoint'а (fetchUpdates / fetchState /
    subscribeLive / postPending), сейчас реализован InProcessTransport
    (in-memory), задел под HTTP+WS.

ksqlite-бэкенд (commonMain, ksqlite 0.1.4):
  - SqliteEventLog — append-only с UNIQUE(event_id) для идемпотентности.
  - SqliteEventLogReplica — клиентская сторона (processed_event_id,
    pending_event, sync_state singleton-row).
  - SqliteStateStore — chat_state / message_state + replaceState.
  - SqliteSyncBundle — фабрика для тестов.

CursorExpired split (§6.3 спеки):
  - WRONG_EPOCH — сервер сменил эпоху (wipe/restore/миграция).
  - TOO_OLD — компакция унесла события ниже floor.
Оба → клиент обязан сделать replaceState (sync() делает это сам).

EventLogWriter.beginNewEpoch(newEpoch): Cursor — атомарная смена эпохи
на сервере: DELETE event_log + reset sqlite_sequence + UPDATE
compaction_state, в одной транзакции.

Сборка: jvm + linuxX64 (mingwX64 компилируется, тесты на linux хосте
пропускаются — ksqlite имеет нативные бинарники только под эти три).

Тесты (74, commonTest, проходят на jvm и linuxX64):
  - CursorTest (14) — парсинг, валидация, isAfter/isBefore/compareTo.
  - EpochMismatchTest (6) — WRONG_EPOCH / TOO_OLD → full resync.
  - InvariantsTest — 7 инвариантов спеки на in-memory бэкенде.
  - IdempotencyTest — повторный applyRemote по eventId = no-op.
  - EditDeleteTest — message edit/delete через события.
  - CompactionAndOrderTest — compact сдвигает minAvailableCursor.
  - OfflineWriteTest — pending отправляется при следующем sync().
  - AssistantTest — LocalAssistant возвращает Flow<DomainEvent>.
  - KsqliteSyncSpecTest (15) — все спец-тесты на ksqlite.
  - KsqlitePersistenceTest — file-based persistence.

Существующий код (:outbox-api / :journal-api / :client / :server) не
трогаем — это чистая референсная реализация для последующей миграции.
2026-10-02 01:08:49 +03:00
..

:sync-core

Референсная реализация ТЗ SYNC-SYSTEM.md в изоляции от существующего кода :outbox-api / :journal-api / :client / :server. Цель — проверить, что спека действительно работает, и выработать API для переиспользования как в in-process (встроенный агент), так и в удалённом (HTTP/WebSocket) сценариях.

Что здесь

Слой Где Что
Domain types commonMain DomainEvent (sealed), LoggedEvent, PendingEvent, Chat/Message/ChatState/StateSnapshot, CompactionState, SyncState
Интерфейсы commonMain EventLog / EventLogWriter / EventLogReplica, StateStore, Reducer, Assistant, SyncTransport
In-memory бэкенд commonMain InMemoryEventLog (одновременно Writer + Replica), InMemoryPendingQueue, InMemoryCompactionState, InMemorySyncState, DefaultChatReducer
SyncEngine commonMain Алгоритм §5.2 спеки (sync() + live-подписка)
Transport commonMain InProcessTransport — имитация HTTP/WS поверх in-memory writer'а
Assistant commonMain Assistant интерфейс + LocalAssistant (in-process генератор) + RemoteAssistant (заглушка под HTTP)
Tests commonTest SyncSpec (abstract) + спец-тесты под все 7 инвариантов и ключевые сценарии §8 спеки
ksqlite-бэкенд commonMain SqliteEventLog / SqliteStateStore / SqlitePendingQueue / SqliteCompactionState / SqliteSyncState
ksqlite интеграционные тесты jvmTest KsqliteSyncSpecTest — тот же SyncSpec, но поверх in-memory SQLite-коннекшна

Границы

  • Не реализует HTTP/WS/SSE (это отдельный слой поверх SyncTransport).
  • Не реализует LLM (assistant'ы — детерминированные генераторы).
  • Не реализует streaming-протокол ответа (стриминг дельт — отдельная концепция, в спеке его нет).
  • Не реализует мультидевайс-race (origin помечает события, но спека молчит о том, как разрешать конкурентные правки одного чата с двух устройств; last-write-wins по seq зашит в редьюсере).

Сборка

./gradlew :sync-core:build           # компиляция + все тесты
./gradlew :sync-core:jvmTest         # только JVM (включая ksqlite интеграцию)
./gradlew :sync-core:allTests        # все цели

Статус

Спека реализована вчерне, инварианты §2 покрыты тестами в commonTest. После стабилизации API и ksqlite-слоя возможна миграция существующих :outbox-api/:journal-api/:client/:server на эту модель (или нет — тогда :sync-core останется референсом и песочницей для новых идей).