feat(map): add persistent gateway and ontology package

This commit is contained in:
Codex
2026-07-13 17:14:34 +03:00
parent d196d4b0c7
commit e527812826
21 changed files with 1323 additions and 9 deletions
+87
View File
@@ -0,0 +1,87 @@
# NODE.DC Map Gateway
`map-gateway` — единый платформенный boundary для map providers. Он не является частью Engine и не принадлежит отдельному Module Studio application.
## Что делает
- хранит master `CESIUM_ION_TOKEN` только в process environment;
- выдаёт браузеру только asset-scoped endpoint token для allowlisted ion assets;
- проксирует и кэширует tile/3D Tiles/terrain resources через `GET /api/map/cache?url=<https-url>`;
- может отдать перенесённый локальный Engine snapshot через `MAP_GATEWAY_LEGACY_CACHE_HOSTS`, но никогда не догружает для него cache miss из сети;
- работает с persistent volume, а не с Git или Docker image layer;
- при upstream outage отдаёт ранее сохранённый cache object; offline режим включается только для providers, явно разрешённых их лицензией;
- защищает proxy allowlist-ом upstream hosts, HTTPS-only правилом, лимитами размера и token-free cache key;
- ведёт лёгкий persistent cache index для LRU eviction и health/stats.
## API
```text
GET /healthz
GET /api/map/ion/assets/:assetId/endpoint
GET|HEAD /api/map/cache?url=<encoded-upstream-url>
```
`/api/map/ion/assets/:assetId/endpoint` разрешает только `CESIUM_ION_ASSET_ALLOWLIST`. Ответ содержит runtime asset token, а не master token. Browser использует endpoint вместе с Cesium `DefaultProxy`, направляющим resource requests в `/api/map/cache`.
Ion endpoint metadata также сохраняется в persistent volume с файловыми правами `0600`. Это нужно для cold start в offline: Cesium получает сохранённый asset URL и asset-scoped token, а proxy отвечает только из ранее записанного cache. В metadata никогда не записывается master token.
## Cache modes
- `readwrite` — свежий cache hit отдаётся сразу; stale object обновляется online; при upstream failure возвращается stale copy;
- `readonly` — использует cache, но не записывает новый;
- `offline` — никогда не идёт наружу; cache miss возвращает `504`.
`offline` дополнительно требует, чтобы hostname был явно указан в `MAP_GATEWAY_OFFLINE_PROVIDER_ALLOWLIST`. По умолчанию список пуст: Gateway не превращает Cesium ion или public OSM tile service в offline distribution. Это осознанная provider policy, а не техническая ошибка cache.
Range requests пока transparently проксируются и не записываются как partial cache object. Полные GET responses кэшируются. Это безопасная начальная граница для terrain/3D Tiles; отдельным следующим шагом добавляется range-aware chunk cache после замеров реальных GOS/3D Tiles assets.
## Persistent storage
Platform Compose использует два внешних named volume: mutable `nodedc-platform_map-live-tile-cache` монтируется в `/var/lib/nodedc-map-live-cache`, read-only snapshot `nodedc-platform_map-offline-snapshot` — в `/var/lib/nodedc-map-offline-snapshot`. Их содержимое намеренно исключено из Git. Внешний volume не удаляется через `docker compose down -v`; очистка требует явного `docker volume rm` при остановленном Gateway. При необходимости offline seed доставляется отдельным export/import artifact, а не коммитом runtime cache.
Кэш не способен создать участок карты, который никогда не был скачан. Для целевых офлайн-регионов нужен отдельный licensed dataset/provider и его `seed/prefetch` job: он заранее проходит разрешённые assets, zoom-диапазоны и географические области, записывает их в тот же persistent volume и формирует проверяемый export/import artifact. Эта задача не смешивается с runtime gateway и не помещает binary tiles в историю репозитория.
### Legacy Engine cache: локальный перенос без изменения Engine
В локальной Studio-песочнице существующий Engine cache можно скопировать в persistent volume как неизменяемый входной snapshot. Исходная директория Engine при этом только читается: import job не удаляет, не переименовывает и не изменяет ни одного её файла.
```bash
npm run audit:engine-cache -- /absolute/path/to/nodedc-data/api/cesium/manifest.json
```
Команда audit выдаёт агрегированный отчёт: hosts, число объектов, declared/on-disk размер и отсутствующие файлы. Сам перенос выполняется отдельно, после остановки gateway на время записи index:
```bash
npm run import:engine-cache -- /absolute/path/to/nodedc-data/api/cesium/manifest.json /persistent/map-tile-cache
```
По умолчанию действует режим `no-overwrite`: если объект уже присутствует в новом cache, он остаётся без изменений. `--dry-run` выводит план без копирования, `--refresh` разрешает осознанно заменить уже импортированный объект. Legacy `http` URLs нормализуются до `https`, чтобы совпасть с HTTPS-only Gateway; сами файлы, content-type, etag, timestamp и provenance сохраняются в новом index/import report.
Это sandbox-механизм переноса уже существующей локальной реализации. Для внешней коммерческой публикации потребуется отдельная provider/offline policy; она не блокирует текущую локальную миграцию, но и не должна смешиваться с ней. Runtime cache по-прежнему не попадает в Git или Docker image layer.
## Security and access
В local dev допускается `MAP_GATEWAY_ALLOW_ANONYMOUS=true`. В production он должен быть `false`; Caddy/Auth BFF проверяет Authentik session и передаёт очищенный trusted subject header в private platform network. Нельзя открывать gateway напрямую наружу и нельзя принимать user-provided upstream hosts вне allowlist.
## Required environment
```text
CESIUM_ION_TOKEN=
CESIUM_ION_ASSET_ALLOWLIST=1,96188
MAP_GATEWAY_UPSTREAM_ALLOWLIST=api.cesium.com,assets.ion.cesium.com,tile.openstreetmap.org
MAP_GATEWAY_LEGACY_CACHE_HOSTS=ecn.t0.tiles.virtualearth.net,ecn.t1.tiles.virtualearth.net,ecn.t2.tiles.virtualearth.net,ecn.t3.tiles.virtualearth.net
MAP_GATEWAY_OFFLINE_PROVIDER_ALLOWLIST=
MAP_CACHE_DIR=/var/lib/nodedc-map-cache
MAP_CACHE_MODE=readwrite
```
# Cache topology
`MAP_CACHE_DIR` is the mutable **live** cache. In the Platform compose profile it
is mounted as `map-live-tile-cache` and is the only store that can receive new
upstream objects.
`MAP_OFFLINE_SNAPSHOT_DIR` is optional and mounted read-only as
`map-offline-snapshot`. It is populated only by the controlled Engine cache
import. Requests marked with the `offline` cache profile are served exclusively
from this snapshot; a missing tile returns a cache miss and never reaches an
upstream provider.