docs: NATIVE-COMPATIBILITY.md checklist for linuxX64 bring-up

This commit is contained in:
2026-09-13 18:20:10 +03:00
parent e816d8d9d1
commit adab112b5b
+147
View File
@@ -0,0 +1,147 @@
# Совместимость с native (linuxX64) — чеклист
Цель: `:standalone` собирается и запускается как `linuxX64` executable (и потенциально
остальные нативные таргеты KMP). Текущее состояние — JVM-only executable на
`runJvm`-таске.
Формат: `- N. [ ]` — задача; `- N. [x]` — выполнена; `- N. [-]` — отменена/не нужна.
Решения (что выбрали и почему) — курсивом внизу пункта.
---
## Блок 1. Подготовка (уже сделана или тривиальная)
- 1. [x] **Замена `java.util.UUID` на KMP-stdlib (`kotlin.uuid.Uuid`).** Helper `Ids.new(prefix)` в `standalone/src/commonMain/.../persistence/Ids.kt`, три call-site (`ChatAgent`, `ChatConversation`, `SqliteWorkingMemoryStore`). Коммит `e816d8d`.
- 2. [x] **HTTP-движок: `ktor-server-netty` → `ktor-server-cio`.** Netty — JVM-only; CIO — KMP (jvm + linuxX64 + ios/...). Каталог-эntry Netty оставлен в `libs.versions.toml` на случай отката. Коммит `e816d8d`.
## Блок 2. Библиотеки litert-kmp (наши)
- 3. [ ] **`litert-api`: добавить `linuxX64()` в `kotlin{}`.**
Сейчас только `androidTarget() + jvm()`. commonMain уже KMP-ready (только
`kotlinx-coroutines-core`). Объём: одна строка в `litert-api/build.gradle.kts`,
один `litert-api-linuxx64`-артефакт публикуется в Nexus.
- 4. [ ] **`litert-openai`: добавить `linuxX64()` в `kotlin{}`.**
Та же история. commonMain deps (`ktor-client-core`, `ktor-client-cio`,
`kotlinx-serialization-json`) — все KMP с готовыми linuxX64-артефактами.
Объём: одна строка + проверить, что Android-only фичи (если есть) изолированы
в `androidMain` (сейчас модуль не имеет `androidMain`-специфичного кода).
*Каталог-запись `litert-openai-jvm` → переименовать в `litert-openai`*
(чтобы Linux-таргет подтягивал `-linuxx64`-вариант автоматически, не нужно
ручного `when target`).
- 5. [-] **`litert-google`: linuxX64 — невозможно.** Upstream-блокер: Google
публикует `litertlm-android` (AAR с `.so`) и `litertlm-jvm` (fat-jar с `.so`
для linux/mac/win JVM), но НЕ нативного `litertlm-linuxx64`. Без правки
upstream LiteRT-LM KMP-враппер для linuxX64 не напишется. Варианты:
(a) выкинуть `litert-google` на linuxX64 (`backend=google` → отказ); (b)
альтернативный движок (llama.cpp через JNI/Kotlin/Native);
(c) upstream PR в `litertlm`. *Решение: (a) для v1 linuxX64 — упасть с
понятной ошибкой `LlmBackend.GOOGLE is not supported on this platform`
вместо reflection-fallback на JVM .so, который на linuxX64 не сработает бы.*
## Блок 3. Наш `:server` модуль
- 6. [ ] **Переписать `:server` с `kotlin("jvm")` на `kotlin("multiplatform")` с
`jvm() + linuxX64()`.** Текущий контракт `Route.agentikAgent(agent, path)` —
чистый Ktor Route, не зависит от engine. commonMain: Ktor core + SSE +
ContentNegotiation + kotlinx-serialization-json. jvmMain: текущее
содержимое. linuxX64Main: тот же commonMain + ktor-server-cio engine для
embedded-сервера, если `Main.kt` на linuxX64 будет поднимать сервер сам
(иначе — оставить сервер только в jvmMain/:standalone, а `:server`-маршруты
вызывать из native-обёртки). Объём: ~30 строк gradle + перенос imports.
*Решение: на linuxX64 `:server`-роуты вызываются тем же `Main.kt`-ом через
`embeddedServer(CIO, ...)` — engine-нейтральность уже заложена.*
## Блок 4. Наш `:proto` модуль
- 7. [ ] **`:proto`: заменить `kotlinx.datetime.Instant` на `kotlin.time.Instant`.**
Per Memory #3708 — typealias `kotlinx.datetime.Instant` deprecated в
kotlinx-datetime 0.8.0, нужно перейти на `kotlin.time.Instant`
(он же в commonMain). После: дропнуть `api(libs.kotlinx.datetime)` из
commonMain, и из `libs.versions.toml`. Объём: замена импорта + типов +
возможно `InstantSerializer` в `:server/src/main/.../Serialization.kt`.
## Блок 5. SQLDelight — нативный драйвер
- 8. [ ] **`sqlite-driver` (JVM/JDBC) → `native-driver` для linuxX64.**
В jvmMain: оставить `sqlite-driver` (JDBC over `java.sql.DriverManager`).
В linuxX64Main: добавить `native-driver` — KMP driver поверх cinterop
с `libsqlite3` (нужен `-lsqlite3` linker-флаг или `SQLiteBundle` из
`co.touchlab:sqliter` для бандлинга). Создать `SqliteStores`-
нейтральный интерфейс хранилищ (он уже есть в `commonMain`), разделить
`jvmMain/SqliteStores.kt` и `linuxX64Main/SqliteStoresNative.kt` —
оба реализуют один `commonMain`-интерфейс. Объём: ~150 строк
(новый `SqliteStoresNative.kt` + gradle wiring).
*Решение по бандлу: использовать `SQLiteBundle` из
`co.touchlab:sqliter` (KMP-обёртка, тянет свой libsqlite) — без
зависимости от системного libsqlite3, бинарь работает на любом linux.*
- 9. [ ] **Тесты SQLite переписать на commonTest + `expect`/`actual` обёртку.**
`inMemory()`-открытие в `native-driver` использует другой API (нет
JDBC `:memory:`). Объём: один `expect fun openInMemory()` в commonMain +
два `actual` (JDBC `jdbc:sqlite::memory:` / native-driver нативный API).
## Блок 6. Внешние либы (`a2a-server`, `agui-server`) — наши
- 10. [ ] **`agui-server`: проверить KMP-готовность.** Per Memory #3561 — KMP
jvm+native. *Если по факту уже KMP — только подключить `linuxX64`-артефакт в `:standalone`; если JVM-only — пункт 11.*
- 11. [ ] **`a2a-server`: JVM-only → KMP.** Per Memory #3561 — JVM-only
(client/server; shared уже KMP). Без native-таргета весь стек A2A
недоступен на linuxX64. Объём: переписать на `multiplatform`,
выделить engine-нейтральную маршрутизацию. *Альтернатива: на linuxX64
не подключать a2a-server вообще (только `:server`-фасад). Решение
отложено — зависит от того, нужен ли нативный MCP-юзер-кейс вообще
подключаться к A2A.*
## Блок 7. `:standalone` — добавить native target
- 12. [ ] **`standalone/build.gradle.kts`: добавить `linuxX64()` и
`linuxX64Main`/`linuxX64Test` source-sets.** В `linuxX64Main`:
`:server` (после п.6), `litert-openai` (после п.4), `litert-api`
(после п.3), `ktor-server-cio` (engine), `ktor-client-cio`,
`kotlin-sdk-client` (MCP — KMP, готов), `kotlinx-io` (KMP, готов).
Исключить `litert-google` (п.5) и `a2a-server` (п.11) из linuxX64Main.
- 13. [ ] **`Main.kt`: разделить на `commonMain` (бизнес-логика: создание
ChatAgent, MCP, LlmConfig) + `jvmMain`/`linuxX64Main` (выбор engine,
embeddedServer).** embeddedServer(CIO, …) — один и тот же код на обеих
платформах, KMP-нейтрален. Главное отличие — `SqliteStores.open()` и
обработка GOOGLE-бэкенда на linuxX64 (fail-fast).
## Блок 8. Тесты и CI
- 14. [ ] **`linuxX64Test` source-set.** Покрыть те же 44 теста на
native: должен зелёный прогон через `./gradlew :standalone:linuxX64Test`.
SQLDelight-тесты на native требуют `libsqlite3`/`SQLiteBundle`
на машине (CI-linux предоставляет).
- 15. [ ] **Smoke e2e на linuxX64.** Запустить
`./gradlew :standalone:runLinuxX64` локально, проверить
`POST /agentik/conversations` → 201, цикл с реальным LLM через OpenAI
backend; убедиться что LiteRT-LM GOOGLE не подключается (отказ с
понятной ошибкой).
## Блок 9. Дистрибуция native-бинаря
- 16. [ ] **`runLinuxX64`-таска через KMP binaries DSL.** После добавления
`linuxX64()` KMP генерирует её автоматически — нужно только убедиться
что `binaries { executable { mainClass.set("pw.binom.agentik.standalone.MainKt") } }`
работает для linuxX64 (по аналогии с уже настроенным `runJvm`).
- 17. [ ] **Дистрибутив `.tar`/`.zip`/`AppImage/Docker`.** KMP
`assembleDist` для linuxX64 делает tar.gz/zip. Для прод-дистрибуции —
Docker-образ (`FROM gcr.io/distroless/cc`) или AppImage. Объём:
~50 строк Dockerfile + GitHub Action.
---
## Что НЕ блокирует linuxX64 (закрыто или тривиально)
- `kotlinx-coroutines-core` — KMP linuxX64 ✓
- `kotlinx-serialization-json` — KMP linuxX64 ✓
- `kotlinx-io` — KMP linuxX64 (используется в MCP stdio-transport) ✓
- `kotlin-sdk-client` (MCP) — KMP full targets ✓
- `ktor-server-core`, `ktor-server-sse`, `ktor-server-cio` — KMP linuxX64 ✓
- `ktor-client-core`, `ktor-client-cio` — KMP linuxX64 ✓
- `pw.binom.uuid` — **отсутствует** в проекте (используем `kotlin.uuid`) ✓
## Жёсткие блокеры (не обойти без upstream-работы)
- `litertlm-jvm` → нужен `litertlm-native` от Google (см. п.5)
- `a2a-server` JVM-only (см. п.11) — нужно переписать или явно не подключать
- `agui-server` KMP-готовность под вопросом (см. п.10)