# ADR: Gelios adapter в External Provider Data Plane Статус: **superseded**. Дата: 2026-07-13. Владелец решения: NODE.DC Platform. > Заменено 2026-07-14 документом > [`ADR_L2_OWNED_EXTERNAL_CONNECTORS.md`](ADR_L2_OWNED_EXTERNAL_CONNECTORS.md). > Gelios остаётся domain/ontology примером, но не получает отдельный > provider-owned gateway: fetch, mapping и collection policy живут в его L2 > connector instance; Platform предоставляет нейтральный Data Plane. ## Контекст Gelios Pro поставляет непрерывную телеметрию, конфигурацию устройств и пространственные данные. В текущем доступе подтверждены 107 видимых units, из них 105 с `lastMsg`; окончательный connection scope должен быть зафиксирован allowlist-ом владельца. Это не данные Ontology Core и не данные Tasker. Они требуют отдельного контура для текущего состояния, истории, геозапросов, аналитики и будущих прогнозов. Gelios является первым provider adapter, который подчиняется общему `docs/ADR_EXTERNAL_PROVIDER_DATA_PLANE.md`: connection instance хранит connection scope/policy, Engine Credentials владеет provider secret, gateway владеет storage/read-моделью, а ontology описывает значения, но не runtime data. Физические trike-команды существуют в домене, но **не входят в ingestion или тестирование**. Для них позднее потребуется отдельный, ручной и аудируемый контур. ## Решение 1. Создать отдельный сервис `platform/services/gelios-gateway` как storage/read gateway: - не хранит и не получает provider token; - принимает только аутентифицированный safe-read intake от защищённого Engine L2 Collector workflow; - применяет allowlist и field policy до записи; - нормализует ответ в сущности пакета `gelios`; - публикует read-модель для Engine L2 workflow и Map View. 2. В Engine появляется отдельный Gelios Credential и NDC Agent L2 collection profile: - credential скрывает access/refresh pair и его lifecycle; - credential привязан только к намеренно пошаренному Collector workflow; - Collector содержит allowlist read-capabilities и не имеет command transport; - ни секрет, ни provider response без нормализации не передаются в ontology, UI, Gateway или другие workflow. 3. Выделить сервису собственную БД `gelios-postgres`, не деля её с Tasker, Authentik, Notification Core или Ontology Core. В будущей multi-tenant форме эта БД обслуживает несколько Gelios connection instances, но все records разделены `tenant_id` и `connection_id`. 4. Базовый движок: PostgreSQL 16 с расширениями TimescaleDB и PostGIS. - Timescale hypertable хранит временные ряды и автоматически делит их по времени. - PostGIS хранит нормализованные точки и геометрии зон, а не renderer-объекты Cesium. - Данный выбор покрывает транзакционную конфигурацию, realtime upsert, исторические запросы, SQL-аналитику и географию одним контуром. 5. **Не** использовать RabbitMQ Tasker как общую шину Gelios. Если измерения покажут, что прямой writer или число независимых потребителей не справляются, добавить в Gelios-контур NATS JetStream с durable pull consumers. Он даст replay, acknowledgement и контролируемое удержание сообщений. 6. Engine level-2 Collector — единственная точка provider access; остальные workflow являются потребителями read-модели. Ontology Core остаётся только словарём и контрактами. ## Целевой поток ```text Gelios REST (safe read only) -> NDC Agent L2 collection profile + protected credential -> authenticated normalized intake -> Gelios Gateway: scope -> field policy -> normalizer -> gelios-postgres: current state + immutable telemetry history -> [при необходимости] NATS JetStream -> `fleet.positions.current.v1` read API / realtime subscription -> NDC workflow level 2 -> Map View semantic binding -> Cesium renderer adapter ``` Ни один шаг не получает права отправить команду устройству. Красный command-domain находится вне этого потока; metadata каталога команд сохраняется, но send transport не создаётся. ## Модель хранения v0 | Слой | Назначение | Минимальные записи | | --- | --- | --- | | Контроль | граница и повторяемость сбора | `access_scope`, `collection_run`, `ingestion_cursor`, endpoint/response metrics | | Каталог | стабильные сущности и их конфигурация | `unit`, `unit_group`, `tracker_device`, sensor/maintenance/custom-field definitions | | Current state | одна актуальная запись на unit для карты и интерфейса | `unit_current`, `position_fix`, approved operational status | | History | неизменяемые события с временем наблюдения и получения | `telemetry_snapshot`, selective `sensor_reading`, restricted raw payload reference | | Spatial | геометрии для запросов, не Cesium graphics | point/track/geozone with SRID 4326 | | Analytics | роллапы и признаки, не запросы по всему raw | hourly/daily aggregates, feature sets, model runs | | Audit | попытки, ошибки, политика, будущие команды | collection audit; separate command audit later | `unit_current` обновляется idempotently по `unitSubjectId`. История записывается append-only с ключом дедупликации, включающим provider unit id, observed time и fingerprint сообщения. Все временные таблицы имеют `observed_at` и `received_at`: задержка поставщика не должна переписывать фактическое время на карте. ## Индексы и жизненный цикл - Основной путь истории: `(unit_subject_id, observed_at DESC)`. - Пространственный индекс только для нормализованной geography/geometry; рендер-кэши в БД не храним. - Сырые `params`/raw messages — restricted, отдельно от публичной Studio read-model. - Политики retention, downsampling и резервного копирования должны быть утверждены до запуска history backfill. Они зависят от фактических msg/s, размера payload, нужной глубины истории, RPO/RTO и стоимости хранения. - Для предиктива держать recent/raw слой и отдельные часовые/дневные агрегаты. Timescale continuous aggregates позволяют сохранять длительную агрегированную историю после сокращения raw при корректно согласованных refresh и retention политиках. ## Нулевая итерация без лишней инфраструктуры 1. Зафиксировать owner-approved allowlist и видимые поля. 2. Сделать только safe-read Engine Collector с ограничением по scope, rate limit, paging и cursor. 3. В течение согласованного окна измерить: сообщений/сек, размер ответа, lag, дубликаты, задержку записи и нагрузку запросов карты. 4. На фактах включить Timescale hypertables, PostGIS и retention policy; после этого решить, нужен ли JetStream сразу. 5. Подать только `fleet.positions.current.v1`/approved current position в Map View. Историю и raw не отдавать в renderer напрямую. ## Что не решено этим ADR - окончательное правило connection scope; - частота polling/возможность provider push; - сроки хранения raw, нормализованной истории и агрегатов; - RPO/RTO, репликация и production backup plan; - допуск к ручному command gateway. До отдельного решения отправка команд запрещена. ## Обоснование и источники - [Timescale hypertables](https://docs.timescale.com/use-timescale/latest/hypertables/) — временные таблицы PostgreSQL автоматически партиционируются по времени. - [Timescale self-hosted installation](https://docs.timescale.com/self-hosted/latest/install/) — расширение разворачивается как self-hosted PostgreSQL-контур; production требует backup/PITR и HA-плана. - [Retention и continuous aggregates](https://docs.timescale.com/use-timescale/latest/data-retention/data-retention-with-continuous-aggregates/) — raw и агрегаты требуют согласованных lifecycle-политик. - [PostGIS](https://postgis.net/docs/en/) — PostgreSQL-расширение для spatial types и GiST R-tree индексов. - [NATS JetStream consumers](https://docs.nats.io/nats-concepts/jetstream/consumers) — durable consumers дают acknowledgement, повторную доставку и recovery; рекомендуются pull consumers для новых масштабируемых обработчиков.