# 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 для новых масштабируемых обработчиков.
