52 KiB
Foundry Map Page + Cesium: production canon
Статус: действующий production-контракт
Область: Module Foundry Map Page, Platform Map Gateway, общий TileCache, DC AMD Proxy и DC AMD Connector
Контрольная дата: 2026-07-16
Этот документ — воспроизводимый канон интеграции Cesium в NODE.DC. Он описывает не только текущий Foundry Map Page, но и обязательные границы для следующих продуктов NODE.DC, которым потребуются Cesium Terrain, imagery, 3D Tiles или другие разрешённые provider assets.
Текущий проверенный deploy baseline:
dc-amd-proxy-transport-lifecycle-20260716-005;platform-map-gateway-hot-path-20260716-015;module-foundry-map-hot-path-20260716-012.
Идентификаторы baseline нужны для аудита. Их нельзя повторно использовать для новых изменений: каждый следующий deploy получает новый patch id и новый digest.
1. Главные инварианты
- Browser никогда не получает Cesium Ion master token, asset-scoped token, Bing key, Map Gateway admin secret или egress/connector secret.
- Foundry не хранит Cesium token в
.env, runtime layout, Application Manifest, browser storage, deploy artifact или исходном коде. - Единственный владелец Cesium master token и provider endpoint credentials — private Platform Map Gateway.
- Все browser-запросы карты идут same-origin через Foundry BFF. Browser не обращается напрямую ни к Map Gateway, ни к AMD Proxy, ни к Cesium/Bing.
- Один Platform Map Gateway и один NAS-resident mutable TileCache обслуживают все Foundry Applications, страницы, пользователей и browser-инстансы.
- Warm cache hit читается прямо с NAS и не требует Ion token refresh, AMD Proxy, Windows-машины, VPN или доступности Cesium.
- Только cold miss или явный refresh проходит по внешнему маршруту
Map Gateway → DC AMD Proxy → DC AMD Connector → VPN → Cesium/Bing. - NAS networking, DNS, default route, Tailscale и VPN не изменяются. Через AMD-машину идёт только allowlisted Cesium/Bing traffic.
- TileCache не лежит в Git, Docker image layer или application volume. Это отдельный persistent NAS bind directory.
- Записанный объект не удаляется автоматически и не перезаписывается без явного
nodedc_cache_refresh=1. - Partial response никогда не становится cache entry. Object публикуется atomic rename, а fill считается завершённым только после durable snapshot индекса.
- Все deploy-изменения доставляются data-only artifact через canonical runner: сначала
planточного файла, затемapplyэтого же файла. Live edit и ручнойdocker compose upна NAS не являются deploy-каноном.
2. Источники истины в репозитории
Канон должен обновляться вместе с этими файлами:
- Foundry BFF и admin settings:
server/catalog-server.mjs; - Foundry session/auth boundary:
server/nodedc-auth.mjs; - UI TileCache и health state:
apps/catalog/src/MapFixturePreview.tsx; - Cesium adapter и provider startup:
apps/catalog/src/CesiumMapRenderer.tsx; - Platform provider/cache boundary:
../../platform/services/map-gateway/src/server.mjs; - Map Gateway runtime topology:
../../platform/infra/synology/docker-compose.platform-http.yml; - NAS egress service:
../../platform/services/dc-amd-proxy/server.mjs; - AMD egress installation, recovery and migration runbook:
../../platform/services/dc-amd-proxy/OPERATIONS.md; - Windows connector:
../../platform/services/dc-amd-connector/; - одноразовое pairing:
../../platform/tools/dc-amd-pair/; - canonical artifact builders:
../../platform/infra/deploy-runner/.
Если текст этого документа расходится с уже задеплоенным кодом, сначала фиксируется расхождение в Ops, затем синхронно исправляются код, тесты и этот канон. Нельзя молча считать устаревший текст runtime-поведением.
3. Архитектура и владельцы
Authenticated browser
│ same-origin GET/HEAD + session cookie
▼
Foundry BFF
│ trusted subject header, no provider credential
▼
Platform Map Gateway
├─ warm hit ───────────────► NAS live TileCache ─► browser
│
└─ miss / explicit refresh
│ private egress token
▼
DC AMD Proxy on NAS 172.22.0.222:8790
│ authenticated CONNECT transport
▼
DC AMD Connector on Windows 172.22.0.183:8791
│ workstation VPN exit
▼
Cesium Ion / Cesium assets / Bing imagery
│ streamed response
├─► browser immediately
└─► private temp file ─atomic rename─► NAS TileCache
3.1 Responsibility table
| Компонент | Владеет | Не владеет |
|---|---|---|
| Map Page | provider-neutral scene, presentation, camera, cache intent | tokens, upstream host policy, cache files, VPN |
| Cesium adapter | преобразование scene в Cesium objects, запрос allowlisted asset endpoints | master token, direct provider networking |
| Foundry BFF | user session, same-origin proxy, admin authorization, HMAC signing | provider credentials, TileCache, VPN route |
| Map Gateway | provider allowlist, credential injection, endpoint metadata, TileCache policy/index, upstream diagnostics | UI layout, Windows VPN configuration |
| NAS live TileCache | общие immutable-by-default provider objects и индекс | Git source, per-user state, offline license |
| DC AMD Proxy | узкий authenticated egress, pooling, retry/redirect safety, transport metrics | general proxying, NAS route, provider token persistence |
| DC AMD Connector | CONNECT byte forwarding через текущий Windows VPN | provider URL parsing above host:443, master token, NAS routing |
| Windows workstation | стабильный LAN endpoint, Docker Desktop, VPN exit | Foundry/Map Gateway runtime state |
| deploy runner | root-owned secrets, runtime directories, atomic artifact apply/rollback | application UI, token entry |
3.2 Один writer для общего cache
Текущая topology предполагает один активный map-gateway process, который является единственным writer для index.json. Все Foundry-инстансы используют его через private Docker network.
Масштабирование Foundry горизонтально безопасно: cache общий. Горизонтальное масштабирование самого Map Gateway без доработки запрещено, потому что tile singleflight и coalescing индекса сейчас process-local. Перед запуском нескольких Gateway replicas нужен распределённый per-key lock и согласованный single-writer/transactional index.
4. Два независимых маршрута
4.1 Control plane: настройка Cesium Ion
Foundry admin browser
→ PUT /api/platform-settings/cesium-ion
→ Foundry revalidates admin session
→ HMAC-signed PUT /api/map/admin/cesium-ion
→ Map Gateway verifies assets 1, 2, 96188
→ atomic private token write
Порядок принципиален:
- UI принимает новое значение как password field по существующей HTTPS-сессии.
- Foundry повторно проверяет server-side роль; скрытая кнопка сама по себе не является защитой.
- Foundry не сохраняет token, а формирует HMAC v2 по method, pathname, timestamp, actor id, SHA-256 body и нормализованному Foundry referer.
- Map Gateway принимает admin request только с валидной подписью и timestamp в пределах 60 секунд.
- Candidate token проверяется параллельно по canonical assets:
1— Cesium World Terrain;2— World Imagery/Bing endpoint;96188— Cesium OSM Buildings.
- Только если все три проверки успешны, рабочее значение атомарно заменяется.
- Ошибочный candidate не уничтожает предыдущий рабочий token.
- Browser получает только
configured,verification,updatedAt,updatedBy; значение token не возвращается.
Успешная rotation увеличивает внутреннее поколение token, очищает in-memory и disk endpoint credentials для allowlisted assets и не трогает уже записанные tile objects. In-flight refresh старого поколения не может вернуть старый credential после rotation.
4.2 Data plane: рендер карты
При старте Map Page Cesium adapter независимо и параллельно запрашивает imagery, terrain и buildings. Отказ imagery не блокирует terrain; отказ buildings не останавливает остальную сцену.
GET /api/map/runtime-config
GET /api/map-gateway/api/map/ion/assets/2/endpoint
GET /api/map-gateway/api/map/ion/assets/1/endpoint
GET /api/map-gateway/api/map/ion/assets/96188/endpoint
Endpoint response содержит публичный URL, attribution, тип и credentialMode: "gateway". Затем Cesium DefaultProxy направляет все derived resources через:
/api/map-gateway/api/map/cache?url=<encoded-provider-url>
Master token или asset credential в этом URL отсутствует. Map Gateway добавляет credential server-side только после cache lookup и только если действительно нужен upstream request.
5. Credential model
5.1 Cesium Ion master token
Canonical location внутри mutable Map Gateway volume:
/var/lib/nodedc-map-live-cache/secrets/cesium-ion-token
Физически на NAS:
/volume1/docker/nodedc-platform/map-gateway/live-tile-cache/secrets/cesium-ion-token
Directory создаётся с mode 0700, token и metadata — 0600. Private NAS file имеет приоритет над transitional CESIUM_ION_TOKEN bootstrap после restart.
Нельзя:
- помещать token в Foundry
.env; - собирать его во frontend bundle;
- вставлять в Application Manifest;
- отправлять в chat, Ops comment, diagnostic archive или deploy artifact;
- читать его обратно через UI;
- включать
live-tile-cache/secretsв обычный tile export или SMB-операции.
5.2 Map Gateway admin secret
Runner-owned path:
/volume1/docker/nodedc-platform/secrets/map-gateway-admin-secret
Он монтируется read-only в Foundry и Map Gateway как /run/nodedc-secrets/map-gateway-admin-secret. Это ключ межсервисной подписи, а не Cesium token и не продуктовая настройка. Он никогда не вводится пользователем.
5.3 Map egress proxy token
Runner-managed path:
/volume1/docker/nodedc-platform/secrets/map-egress-proxy-token
Map Gateway читает его из read-only mount и ставит в x-proxy-token только при запросе к DC AMD Proxy. Browser и Windows connector его не получают.
5.4 AMD connector access token
Windows installer создаёт локальный random value в:
C:\NODEDC\dc-amd-connector\runtime\connector-access
Одноразовый pairing script читает его внутри запущенного container и передаёт напрямую на NAS без печати. NAS сохраняет paired value в private runtime dc-amd-proxy; deploy artifact его не содержит.
5.5 Asset-scoped endpoint credentials
Ответ Ion endpoint API может содержать временный accessToken либо Bing key. Map Gateway хранит эти значения только в памяти и private files:
live-tile-cache/ion-endpoints/<assetId>.json
Файлы имеют mode 0600 и считаются service-sensitive. Master token в них не записывается.
Перед выдачей endpoint browser-у Gateway удаляет credential query parameters. Перед upstream miss Gateway:
- снова удаляет user-supplied
token,key,signatureи аналогичные параметры; - сопоставляет resource с endpoint по точному origin и наиболее длинному разрешённому path scope;
- для file endpoint, например
tileset.json, разрешает sibling resources только внутри его directory; - никогда не превращает root-level file endpoint в credential scope всего host;
- добавляет соответствующий asset credential или Bing key.
Endpoint cache имеет TTL 300 секунд и refresh-ahead 60 секунд по умолчанию. Refresh одного asset singleflight-ится. Истёкший JWT не выдаётся, не сохраняется и не подставляется.
5.6 Referer restrictions
Если Cesium token ограничен URL/referer policy, production FOUNDRY_PUBLIC_URL должен совпадать с разрешённым Foundry origin. Foundry делегирует только собственный configured origin, а не произвольный browser header. Нормализованный referer входит в HMAC admin request и передаётся в provider requests.
Прямая terminal-проверка token без того же referer может вернуть 401, хотя production flow работает. Каноническая проверка — через Foundry settings/Map Gateway, потому что она воспроизводит реальный транспорт и проверяет все три обязательных asset.
6. Физическое устройство TileCache
6.1 NAS paths
Mutable live cache:
/volume1/docker/nodedc-platform/map-gateway/live-tile-cache
Container mount:
/var/lib/nodedc-map-live-cache
Read-only offline snapshot:
/volume1/docker/nodedc-platform/map-gateway/offline-snapshot
Container mount:
/var/lib/nodedc-map-offline-snapshot
Обе host directory создаёт root-owned deploy runner. docker compose down, image rebuild и service restart их не удаляют.
6.2 On-disk format
live-tile-cache/
├── index.json
├── objects/
│ └── <first-two-hash-chars>/
│ └── <sha256-cache-key>.bin
├── ion-endpoints/
│ ├── 1.json
│ ├── 2.json
│ └── 96188.json
└── secrets/
├── cesium-ion-token
└── cesium-ion-token.metadata.json
objects и index.json — собственно TileCache. ion-endpoints и secrets — private service state; они не являются пользовательскими cache objects.
index.json version 1 содержит для каждого SHA-256 key:
- relative file path;
- byte count;
- content type;
- provider ETag, если он корректен;
savedAt,lastAccessAt,expiresAt.
Binary object сохраняется с расширением .bin независимо от media type; content type берётся из индекса. Cache key — SHA-256 canonical provider URL.
6.3 Canonical URL and deduplication
До вычисления key удаляются:
nodedc_client_revision;nodedc_cache_profile;nodedc_cache_mode;nodedc_cache_refresh;- credential-like query parameters.
Query parameters сортируются. Bing subdomains ecn.t0…ecn.t3 нормализуются в один logical host, чтобы одинаковый quadkey не создавал четыре копии.
Следствие: token rotation, новый browser build или другой Foundry Application не создают новый tile object для тех же provider bytes.
7. Product semantics of cache controls
7.1 Кэшировать live-данные выключено
Adapter добавляет nodedc_cache_mode=passthrough. Запрос всё равно проходит через Foundry и Map Gateway, поэтому credential/security boundary сохраняется, но live persistent cache не читается и не записывается.
Это не direct browser-to-Cesium режим.
7.2 Cache включён + Не перезаписывать уже полученный cache включено
Это основной steady-state режим:
- hit немедленно отдаётся с NAS;
- miss идёт к official upstream и после полного получения дописывается;
- существующий объект не заменяется даже после его provider TTL;
- отключение AMD/VPN не влияет на уже записанную область.
Галка не замораживает cache целиком. Она запрещает перезапись существующих key, но новые tiles, которые пользователь впервые открыл, продолжают долетать и записываться на NAS.
7.3 Cache включён + Не перезаписывать выключено
Adapter добавляет nodedc_cache_refresh=1 в provider resource root. Derived requests текущего Application могут заменить существующие objects. Это явно разрешённый update mode, а не автоматический фоновый refresh всего cache.
Обновляются только ресурсы, которые реально запросил renderer; мировой dataset не обходится целиком.
7.4 Обновить текущий viewport
Это одноразовый refresh. Renderer пересоздаётся с nodedc_cache_refresh=1, помечает root provider resources текущего view, после первого render возвращается к сохранённой steady-state policy.
7.5 Gateway-level modes
MAP_CACHE_MODE |
Hit | Miss | Запись |
|---|---|---|---|
readwrite |
NAS | official upstream | да, после полного response |
readonly |
NAS | live pass-through | нет |
offline |
NAS/offline policy | 504 |
нет и никакого egress |
Application UI не может изменить server-level MAP_CACHE_MODE.
7.6 Live vs offline profile
nodedc_cache_profile=live выбирает mutable store. offline выбирает read-only snapshot и никогда не допускает pass-through.
Offline mode дополнительно требует hostname в MAP_GATEWAY_OFFLINE_PROVIDER_ALLOWLIST. Пустой список по умолчанию означает осознанный запрет offline redistribution. Наличие технически сохранённых Cesium bytes само по себе не создаёт право на их offline distribution.
8. Request algorithm
Для каждого /api/map/cache Gateway выполняет следующий порядок:
- Проверяет trusted subject, method и route.
- Парсит и валидирует HTTPS target, host allowlist, URL length и отсутствие embedded credentials.
- Нормализует cache profile/mode/refresh intent и удаляет их из provider URL.
- Вычисляет credential-free canonical key.
- Для
passthroughпропускает persistent store и идёт к upstream через тот же security boundary. - Для normal mode сначала ищет object в выбранном store.
- Если по этому key уже идёт fill/refresh, новый request становится follower и ждёт его commit.
- Offline/legacy profile никогда не превращает miss в неожиданный upstream fetch.
- Normal hit без explicit refresh сразу отдаётся с NAS, в том числе stale hit.
- Только после отсутствия пригодного hit Gateway получает/обновляет endpoint credential и подставляет его в upstream URL.
readonlyотдаёт live response без записи.- Range request проксируется как range и пока не записывается partial object.
- Full miss/refresh запускает streaming fill.
- При provider failure explicit refresh возвращает предыдущий cached object как
live-stale-upstream-error, если он существует. - При заполненном cache cold miss показывается live как
live-pass-through-cache-full; существующие objects не удаляются.
Этот порядок гарантирует ключевой fail-safe: warm cache не зависит от control plane и egress.
9. Concurrency and performance
9.1 Что происходит при 20 одновременных пользователях
Для одного и того же missing key:
- Первый request становится leader и открывает один upstream stream.
- Response body делится через stream tee.
- Client branch начинает передаваться первому browser сразу после provider headers.
- Cache branch независимо пишется во временный файл.
- Остальные request того же key становятся followers и не создают новые provider downloads.
- После atomic commit followers читают опубликованный NAS file.
- Если один browser закрыл вкладку, server-owned cache fill не отменяется и продолжает обслуживать других.
Для разных keys requests выполняются параллельно. Capacity reservations сериализуют только расчёт общего объёма, а не весь network path.
9.2 Publication safety
Cache fill:
- пишет только в private
*.tmp; - считает фактические bytes и прерывает object сверх лимита;
- резервирует capacity с учётом replacement;
- делает atomic rename только после полного EOF;
- затем добавляет entry в memory index и ждёт durable index snapshot;
- при abort/error удаляет temp и не публикует index entry.
9.3 Index strategy
Warm hit не обновляет lastAccessAt и вообще не переписывает index.json. Это осознанное следствие no-eviction policy: запись полного O(N) JSON на каждый 20 KB tile делала warm views медленнее provider.
Завершения разных new objects объединяются в короткое 10 ms окно. Один atomic index snapshot включает все накопленные revisions; waiter каждого object завершается только после durable revision.
9.4 ETag and browser cache
NAS hit получает безопасный ETag: валидный provider ETag либо deterministic NODE.DC ETag. If-None-Match возвращает 304 без чтения body и без записи index.
Foundry BFF разрешает browser cache только для подтверждённых persistent hit/stale states:
Cache-Control: private, max-age=300, stale-while-revalidate=60
Vary: Cookie
Provider pass-through, endpoint metadata, health, explicit refresh и errors получают no-store. Shared reverse proxy не должен раздавать authenticated map response между пользователями.
9.5 AMD connection pool
DC AMD Proxy держит отдельный HTTP/1.1 keep-alive pool на каждый approved origin:
- максимум 8 active sockets per origin;
- максимум 4 idle sockets per origin;
- LIFO reuse;
- keep-alive 30 секунд;
- excess requests ждут в agent queue за bounded socket pool; её фактический пик обязательно контролируется через
maxQueuedRequests.
Это устраняет новый NAS → AMD CONNECT → TLS handshake для каждого tile. При насыщении throughput ограничивается VPN/provider, а не созданием неограниченного числа tunnel. maxQueuedRequests, queue/connection/TTFB/total timings показывают, где возникает задержка.
Только idempotent GET/HEAD, потерявший уже reused keep-alive socket, повторяется один раз. Fresh-socket failure не ретраится автоматически. При cross-origin redirect Authorization удаляется; master/API bearer не может уйти с api.cesium.com на assets/Bing origin.
10. Timeouts, aborts and recovery
10.1 Foundry BFF
- headers deadline: 15 секунд по умолчанию;
- body idle deadline: 30 секунд без прогресса;
- body timeout сбрасывается каждым chunk;
- после upstream EOF медленный browser drain не ограничивается абсолютным таймером;
- browser abort отменяет BFF upstream request;
- ошибка после отправки headers закрывает stream, а не пытается отправить второй JSON response.
10.2 Map Gateway
- upstream timeout: 30 секунд по умолчанию;
- warm hit не запускает этот timeout;
- refresh failure с прежним object даёт stale fallback;
- client abort leader response не отменяет cache branch;
- headers-sent error уничтожает только response, не Gateway process.
10.3 DC AMD Proxy
- connector/TLS connection timeout: 20 секунд;
- provider headers/attempt timeout: 30 секунд;
- response body idle timeout: 30 секунд без bytes;
- abort проходит через queue, CONNECT, TLS, headers, redirect drain, retry и body;
- каждый logical request получает ровно один terminal accounting outcome;
- partial bytes учитываются отдельно от complete response bytes.
10.4 Cesium renderer
Provider startup независим. Render error не показывает стандартную Cesium modal с [object Object]; UI получает safe code и делает одну bounded попытку восстановить render loop. Постоянный GPU/browser fault не запускает бесконечный retry.
11. Authentication and authorization
Production Foundry всегда использует Launcher/Authentik session. Map tile burst не должен валидировать Launcher на каждый object, поэтому:
- validation TTL по умолчанию 20 секунд и зажат в production диапазоне 15–30 секунд;
- запросы одной session делят singleflight validation;
- transient Launcher error использует 2-секундный retry backoff;
- last-known-good identity допускается не более 30 секунд grace и только для
GET/HEAD; - mutation во время transient auth failure получает
503; - только явный
{ok:true, active:false}удаляет session/cookie; - timeout, network error и Launcher non-2xx не уничтожают рабочую cookie;
- session хранится process-local, поэтому после Foundry restart старая opaque cookie истекает и нужен новый handoff.
Foundry roles:
nodedc:module-foundry:admin— Platform settings;nodedc:module-foundry:user— обычная работа без settings;nodedc:module-foundry:blocked— deny-first;nodedc:module-foundry:access— transitional user role;nodedc:superadminиuser_root— break-glass admin.
Raw groups не возвращаются browser-у. Map Gateway production route требует trusted x-nodedc-user-id, который создаёт только Foundry BFF/private health check. Gateway нельзя публиковать напрямую в Internet.
12. DC AMD route
12.1 NAS service
dc-amd-proxy работает с network_mode: host, но слушает только 172.22.0.222:8790. Host networking нужен, чтобы one-time pairing видел реальный source IP Windows host; service не bind-ится на все NAS interfaces.
Он принимает:
GET /healthzиGET /status— safe state/metrics;POST /api/pair— один раз и только с configured AMD LAN source;GET|HEAD /proxy/cesium/fetch— только с runner-synchronised map egress token.
Target обязан быть HTTPS port 443 и входить в фиксированный Cesium/Bing allowlist. Direct NAS egress fallback отсутствует (directEgress: false).
12.2 Windows connector
dc-amd-connector bind-ится к стабильному LAN IP 172.22.0.183:8791, а не к OpenVPN address или public exit IP. Windows Firewall разрешает inbound только с NAS 172.22.0.222.
Connector:
- принимает только authenticated HTTP
CONNECT; - разрешает только port 443 и тот же host allowlist;
- не расшифровывает end-to-end TLS и не видит Ion bearer;
- не является системным Windows proxy;
- не меняет routing/DNS/VPN других приложений;
- ограничен 1 CPU, 256 MB, 64 processes и 4096 file descriptors.
Docker Compose использует restart: unless-stopped. Docker Desktop на WSL2 восстанавливается через Startup launcher после входа configured Windows user. До sign-in это не unattended Windows service; в это окно warm TileCache продолжает работать, а cold misses ожидаемо недоступны.
12.3 Pairing and moving to another machine
Первичная установка выполняется из elevated PowerShell внутри проверенного connector package:
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1
После того как NAS proxy показывает state=awaiting_pair, на Windows запускается package из platform/tools/dc-amd-pair:
powershell -NoProfile -ExecutionPolicy Bypass -File .\pair-dc-amd-proxy.ps1
Ожидаемый результат: AMD connector paired with NAS. No secret value was displayed.
Перенос:
- Выбрать новый стабильный LAN IP и проверить VPN на новой машине.
- Установить тот же versioned connector package с явными
-BindAddressи-NasAddress. - Проверить local container health и permitted CONNECT.
- Подготовить новый NAS proxy artifact/config с новым connector host и pair source.
- Реализовать и проверить runner-owned pair-rotation transition: текущий NAS pair write-once и не принимает fresh credential поверх существующего state.
- Через этот transition вывести старую pairing identity, выполнить canonical
plan,apply, one-time pairing и end-to-end validation. - Остановить старый connector только после успешного cold-request acceptance на новой машине.
Текущий runner ещё не предоставляет pair rotation как готовую операцию. До её появления миграция с fresh credential не является полностью канонической: нельзя вручную удалять NAS runtime/connector-access, копировать старый secret, ослаблять /api/pair или одновременно держать две pairing identity. Сначала создаётся отдельная Ops/change задача на audited rotation, затем выполняется перенос по platform/services/dc-amd-proxy/OPERATIONS.md.
13. Health and observability
13.1 Safe checks on NAS
curl -fsS http://172.22.0.222:8790/status
curl -fsS \
-H 'x-nodedc-user-id: healthcheck' \
http://127.0.0.1:18103/healthz
curl -fsS http://172.22.0.222:9920/healthz
Эти команды не печатают secrets. Для endpoint acceptance нужно выводить только safe fields, а не private files:
curl -fsS \
-H 'x-nodedc-user-id: healthcheck' \
http://127.0.0.1:18103/api/map/ion/assets/1/endpoint \
| /usr/local/bin/docker exec -i nodedc-platform-map-gateway-1 \
node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>{const v=JSON.parse(s);console.log(JSON.stringify({ok:v.ok,assetId:v.assetId,type:v.type,credentialMode:v.credentialMode,cache:v.cache}))})"
Если конкретное имя container изменилось, использовать docker compose ps, а не угадывать новое имя.
13.2 Map Gateway health fields
/healthz возвращает:
- live/offline cache entry count and bytes;
mode,writePolicy,maxBytes,atCapacity,persistent;- hit/stale/miss/refresh/passthrough/fallback counters;
- upstream and egress request/failure counters;
- slow upstream count;
- index snapshot count;
- safe
lastFailureи timestamp; ionConfiguredи asset allowlist.
Он не возвращает token, provider URL с credential или endpoint credential.
13.3 AMD status fields
/status обязан показывать:
state=paired;forwarding=amd_connector_only;directEgress=false;- active/idle/queued sockets и pool limits per origin;
- logical, terminal, in-flight, complete, failed and aborted requests;
- opened/failed tunnels, socket reuse, bounded retries;
- complete/partial bytes;
- queue, connection, TTFB, retry, attempt and total timings;
- safe last error code.
Инвариант accounting:
terminalRequests + inFlightRequests == requests
После завершившейся нагрузки inFlightRequests должен вернуться в 0.
13.4 UI health state
Когда Inspector или Layers открыт, Foundry:
- проверяет runtime config и Gateway health каждые 15 секунд;
- держит только один concurrent health request;
- отменяет check через 10 секунд;
- различает runtime HTTP, health HTTP, invalid response, network и timeout;
- сохраняет last-known-good stats как
stale, если новая проверка не прошла; - не даёт запоздалому renderer health overwrite более свежий explicit poll.
Красное сообщение gateway_not_configured относится к отсутствию runtime profile, а не автоматически к AMD/VPN. Код ошибки должен сохраняться до UI, иначе оператор ищет проблему не в том слое.
14. Failure matrix
| Симптом / safe code | Слой | Что продолжает работать | Проверка / действие |
|---|---|---|---|
module_foundry_auth_required |
Foundry session | public health | пройти Launcher handoff; не обходить BFF |
module_foundry_auth_unavailable |
Launcher validation | bounded read-only grace, если ещё активен | Launcher health/internal token; mutation не повторять вслепую |
map_gateway_not_configured |
Foundry runtime config | Foundry shell | проверить NODEDC_MAP_GATEWAY_INTERNAL_URL и private Docker network |
map_gateway_headers_timeout |
BFF→Gateway | shell и уже browser-cached objects | Gateway health, CPU/I/O, request queue |
map_gateway_body_idle_timeout |
BFF stream | прочие resources | Gateway/AMD body progress, VPN stalls |
map_gateway_auth_required |
Gateway trusted subject | admin signed route/health с корректным header | BFF header boundary, не включать anonymous в production |
cesium_ion_not_configured |
Gateway credential store | existing object hits | admin settings; не добавлять token в Foundry env |
cesium_ion_token_verification_failed |
provider/control plane | предыдущий token и cache | assets 1/2/96188 permissions, Foundry referer, AMD route |
cesium_asset_not_allowed |
asset policy | canonical assets | изменить root-owned allowlist только с reviewed use case |
cesium_ion_endpoint_unavailable |
Ion API | warm object hits, usable cached endpoint | AMD state/timings, VPN, provider HTTP status, token/referer policy |
map_egress_amd_connector_not_paired |
NAS proxy pairing | warm hits | /status, затем controlled pairing |
map_egress_amd_connector_timeout |
NAS→Windows | warm hits/stale refresh fallback | Windows host online, LAN/firewall, connector container |
map_egress_amd_upstream_tls_timeout |
Windows/VPN→provider | warm hits/stale refresh fallback | VPN exit, DNS/provider reachability on Windows |
map_upstream_timeout |
Gateway upstream | warm hits/stale refresh fallback | distinguish queue/CONNECT/TLS/TTFB via AMD metrics |
map_cache_capacity_reached |
NAS capacity policy | old hits and live pass-through | provision capacity/export; do not delete arbitrary objects |
map_offline_snapshot_miss |
offline snapshot | other snapshot objects | expected miss; seed licensed dataset, never fall through live |
map_provider_offline_not_permitted |
provider policy | live mode if allowed | legal/provider review and explicit allowlist |
map_cache_object_too_large |
object guard | other objects/live policy | inspect asset type; change limit only after measured review |
live-stale-upstream-error |
explicit refresh failed | previous object | expected resilience; investigate upstream asynchronously |
renderer cesium_render_error |
browser/GPU/Cesium | shell and provider diagnostics | browser console/GPU; one recovery already attempted |
14.1 Reading latency correctly
- Высокий
queueMs, нормальный connection/TTFB: pool saturated; проверить request fan-out и VPN throughput. - Высокий
connectionMs: дорогой NAS→AMD CONNECT или TLS setup; socket reuse недостаточен либо Windows/VPN нестабилен. - Низкий connection, высокий
ttfbMs: VPN/provider latency. - Нормальный AMD timing, высокий Foundry body idle: проблема stream progress между Gateway и BFF либо NAS I/O.
- Много
cacheHits, но медленная карта: проверить BFF/browser ETag, NAS read latency, renderer/GPU; AMD здесь не должен участвовать. egressRequestsрастёт на повторном одинаковом append-only viewport: cache key/refresh policy нарушена либо requests действительно относятся к новым tiles/LOD.
15. Canonical deploy and rollback
15.1 Pre-deploy validation
Foundry:
cd NODEDC_DESIGN_GUIDELINE
node --test server/*.test.mjs
npm run build
Map Gateway:
cd platform/services/map-gateway
npm run test:credential-boundary
npm run test:admin-token-boundary
npm run test:live-cache-fallback
npm run test:streaming-cache-fill
DC AMD Proxy:
cd platform/services/dc-amd-proxy
npm run test:connection-pool
docker compose config >/dev/null
Production images должны дополнительно собираться с нуля до публикации artifact. Ни один test fixture token не переносится в artifact.
15.2 Build data-only artifacts
Из repository root с новыми уникальными patch ids:
node platform/infra/deploy-runner/build-dc-amd-proxy-artifact.mjs \
dc-amd-proxy-<change>-YYYYMMDD-NNN
node platform/infra/deploy-runner/build-map-gateway-artifact.mjs \
platform-map-gateway-<change>-YYYYMMDD-NNN
node platform/infra/deploy-runner/build-module-foundry-artifact.mjs \
module-foundry-<change>-YYYYMMDD-NNN
Artifact обязан содержать только manifest.env, files.txt, payload/**; запрещены secrets, live .env, runtime data, cache objects, symlinks, hooks и AppleDouble ._*.
Создать SHA-256 sidecar, проверить tar listing и скопировать .tgz + .sha256 через SMB в:
/volume1/docker/nodedc-deploy/inbox
15.3 Plan before apply
На NAS:
sudo /usr/local/sbin/nodedc-deploy verify-install
sudo /usr/local/sbin/nodedc-deploy plan \
/volume1/docker/nodedc-deploy/inbox/<exact-dc-amd-proxy-artifact>.tgz
sudo /usr/local/sbin/nodedc-deploy plan \
/volume1/docker/nodedc-deploy/inbox/<exact-platform-artifact>.tgz
sudo /usr/local/sbin/nodedc-deploy plan \
/volume1/docker/nodedc-deploy/inbox/<exact-module-foundry-artifact>.tgz
До apply оператор проверяет digest, component, type=app-overlay, payload/compose roots, services, runtime secret mounts, exact files и state=new. sha-already-applied, неожиданный file или component — стоп, а не повод обходить runner.
15.4 Apply order
1. dc-amd-proxy
2. platform / map-gateway
3. module-foundry
Команды используют те же exact paths, что прошли plan:
sudo /usr/local/sbin/nodedc-deploy apply \
/volume1/docker/nodedc-deploy/inbox/<exact-dc-amd-proxy-artifact>.tgz
sudo /usr/local/sbin/nodedc-deploy apply \
/volume1/docker/nodedc-deploy/inbox/<exact-platform-artifact>.tgz
sudo /usr/local/sbin/nodedc-deploy apply \
/volume1/docker/nodedc-deploy/inbox/<exact-module-foundry-artifact>.tgz
Нельзя использовать apply latest, wildcard или вручную распаковывать payload в live roots. Runner создаёт backup и выполняет component health gate. Ошибка одного слоя должна остановить цепочку до следующего apply.
15.5 Post-deploy acceptance
- Все три containers
runningиhealthy. - AMD
/status:paired,amd_connector_only,directEgress=false. - Gateway
/healthz:cache.persistent=true, ожидаемые paths/limits,ionConfigured=true. - Canonical endpoints 1, 2, 96188 возвращают соответствующие types и
credentialMode=gatewayбез credentials. - Первый cold viewport начинает рисоваться до завершения disk commit.
- Повторный viewport увеличивает cache hits, но не egress requests для тех же objects.
- Выключение Windows VPN не ломает warm viewport; новый uncached viewport даёт диагностируемый transport error.
- Explicit refresh при недоступном provider отдаёт старый object, если он был.
- Foundry UI показывает last-known-good cache stats как stale, а не стирает их generic error.
- Никакой token/key не встречается в browser Network response, logs, artifact listing или Ops comment.
16. Reuse in future NODE.DC products
Новый продукт с Cesium не копирует token handling или TileCache внутрь себя. Он подключается к существующей платформенной capability.
Обязательный шаблон:
- Provider-neutral domain/page contract живёт в продукте.
- Renderer adapter получает только same-origin runtime config и sanitised asset endpoint.
- Product BFF подтверждает свою user/session policy и проксирует только разрешённые Map Gateway routes.
- BFF передаёт trusted subject из server-side identity, а не browser header.
- Все provider resources используют Gateway proxy; direct provider URL без proxy запрещён.
- Новый asset id добавляется в reviewed allowlist с ожидаемым type и acceptance test.
- Cache intent кодируется теми же
profile/mode/refreshsemantics. - Cache физически остаётся общим Platform cache; продукт не создаёт per-instance copy.
- Admin token rotation остаётся одной Platform setting, а не повторяется в каждом продукте.
- Health UI различает runtime, Gateway, cache и egress layers и сохраняет safe error code.
Если продукту нужен другой provider или offline dataset, сначала расширяется Platform Map Gateway policy. Нельзя просто добавить arbitrary host в frontend. Требуются license review, host/path allowlist, credential scope, cache policy, diagnostics и tests.
17. Explicit decisions and forbidden anti-patterns
Решения
- Shared NAS cache выбран вместо cache на browser/Foundry instance.
- Append-only/no-eviction выбран для предсказуемости; capacity exhaustion не удаляет уже собранную карту.
- Cache-first выполняется до credential injection, чтобы offline resilience была реальной.
- First miss streaming выбран вместо «сначала полностью записать, потом показать».
- One Map Gateway writer выбран до появления distributed locking.
- AMD route ограничен provider hosts и не затрагивает NAS default networking.
- Endpoint credentials кэшируются private и refresh-ятся заранее, master token остаётся отдельным.
- Browser cache разрешён только для подтверждённых NAS hits и только private.
Запрещено
- token в browser bundle, source,
.envFoundry, Envoyer-like UI или Application artifact; - direct Cesium calls из browser;
- proxy всего NAS/Windows traffic через VPN ради Cesium;
- open/general-purpose HTTP proxy на AMD или NAS;
- использование changing VPN IP вместо stable AMD LAN IP;
- cache в Docker writable layer или anonymous volume;
- отдельный TileCache на каждого пользователя/Application/Foundry replica;
- credential query в cache key;
- автоматический refresh каждого hit без product intent;
- полный
index.jsonrewrite на каждый warm hit; - новый CONNECT/TLS tunnel на каждый tile;
- безусловный retry non-idempotent request или fresh-socket failure;
- перенос
Authorizationчерез cross-origin redirect; - absolute body deadline, который убивает большой, но прогрессирующий response;
- публикация partial file/index entry;
- автоматический LRU delete без отдельной принятой retention policy;
- silent direct NAS egress, если AMD/VPN недоступен;
- трактовка сохранённых Cesium bytes как разрешения на offline distribution;
- скрытие provider credits на внешнем/коммерческом surface. Текущее скрытие credit container допустимо только как зафиксированный sandbox debt;
- ручной
sudo/live edit вместо exact runnerplan+apply.
18. Known boundaries and next extensions
- Range requests сейчас проходят live и не записываются как chunk cache. Range-aware cache добавляется только после измерения реальных 3D Tiles/Gaussian assets.
- Runtime cache сам не prefetch-ит мир. Для offline regions нужен отдельный licensed seed/prefetch job и versioned export/import artifact.
- Offline snapshot read-only; miss никогда не становится live request.
index.jsonподходит текущему single-writer scale. Multiple Gateway writers требуют нового storage contract.- Windows Docker Desktop recovery начинается после user sign-in. Если потребуется unattended SLA, connector должен стать отдельно спроектированным Windows service/host, а не скрытым изменением текущей схемы.
- DC AMD Proxy можно расширять на новые потоки только отдельным policy change. Нельзя добавлять arbitrary URL forwarding к существующему Cesium route.
- Provider attribution metadata сохраняется, но visible credit surface должен быть возвращён до внешнего release.
19. Definition of done for any Cesium change
Изменение считается завершённым только если:
- credential boundary доказан automated test;
- cache hit не делает provider/egress request;
- same-key concurrency делает один upstream fill;
- abort/partial response не публикует object;
- refresh failure сохраняет старый object;
- endpoint rotation не возвращает credential старого поколения;
- BFF корректно передаёт conditional headers и abort;
- session validation не умножается на tile count;
- AMD metrics сохраняют exact terminal accounting;
- logs не содержат URL query, token, cookie, session/user id или secret;
- production images собираются с нуля;
- data-only artifacts проходят inspection и checksum;
- canonical runner показывает ожидаемый
planдоapply; - post-deploy acceptance проверяет cold, warm, VPN-off и refresh-failure scenarios;
- Ops card и этот документ обновлены вместе с кодом.
Итоговая модель проста: Platform Map Gateway владеет credential и общим NAS TileCache; Foundry владеет authenticated same-origin BFF и UI intent; AMD-машина предоставляет только узкий VPN egress. Warm data всегда возвращается локально, а live route используется только там, где cache действительно не хватает или оператор явно разрешил refresh.