Добавляет :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) не
трогаем — это чистая референсная реализация для последующей миграции.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# `: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` останется референсом и песочницей для новых идей).
|
||||
Reference in New Issue
Block a user