Files
agentik/sync-core/README.md
T
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

47 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `:sync-core`
Референсная реализация ТЗ [`SYNC-SYSTEM.md`](../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` зашит в редьюсере).
## Сборка
```bash
./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` останется референсом и песочницей для новых идей).