feat(foundry): productionize Cesium Map Page and platform runtime
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# NDC Module Foundry — базовый MCP-контур
|
||||
|
||||
## Назначение
|
||||
|
||||
`NDC Module Foundry` — модульный контур поверх канонического `NODE.DC Design Guideline`. Он создаёт и редактирует **экземпляры** готовых страниц в `Applications`; сам `Page Library` и его шаблоны остаются неизменяемым каноном.
|
||||
|
||||
MCP не вводит новую оркестрацию. Доступ выдаётся уже существующему AI Workspace Assistant через его generic entitlement adapter. Поэтому ассистент может продолжать работать с Engine, Ops, Ontology и другими подключёнными модулями по действующим правилам платформы, а Foundry определяет только границы действий **внутри Foundry**.
|
||||
|
||||
## Границы v0.1
|
||||
|
||||
| Разрешено | Не разрешено |
|
||||
| --- | --- |
|
||||
| Просматривать Page Library и экземпляры Applications | Менять канонический Page Library или дизайн-компоненты |
|
||||
| Создавать application instance | Удалять application instance через MCP |
|
||||
| Изменять название, slug и описание instance | Создавать свободный canvas или произвольный React-интерфейс |
|
||||
| Добавлять повторные instances зарегистрированной страницы | Вызывать Engine, провайдера карт или внешний API из Foundry MCP |
|
||||
| Создавать/обновлять provider-neutral map pin bindings | Хранить provider tokens, transport payload или credentials в manifest |
|
||||
| Связывать versioned data product с approved Map entity-stream slot | Хранить provider ID, tenant/connection, endpoint или credential в data binding |
|
||||
|
||||
Каждая запись требует `idempotencyKey`. Операция сохраняется в persistent runtime volume и повторный вызов с тем же ключом и тем же входом вернёт исходный результат без дублирования. Повтор ключа с иным входом завершается конфликтом.
|
||||
|
||||
## MCP endpoint
|
||||
|
||||
`POST /api/mcp`
|
||||
|
||||
Поддерживается MCP protocol `2025-06-18`. Endpoint принимает JSON-RPC `initialize`, `tools/list`, `tools/call` и `ping`.
|
||||
|
||||
Обязательные HTTP-заголовки каждого вызова:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <short-lived server-issued Foundry capability>
|
||||
MCP-Protocol-Version: 2025-06-18
|
||||
```
|
||||
|
||||
Capability выдаётся только сервером Foundry после штатной entitlement-проверки
|
||||
AI Workspace. Она подписана существующим внутренним service credential,
|
||||
привязана к `actorId` и `ownerKey`, живёт не более 10 минут и не даёт worker
|
||||
доступа к самому platform credential. Заголовки с actor/owner от worker не
|
||||
принимаются: контекст извлекается только из подписанной capability.
|
||||
|
||||
Доступные инструменты:
|
||||
|
||||
- `foundry_status`
|
||||
- `foundry_list_applications`
|
||||
- `foundry_get_application`
|
||||
- `foundry_create_application`
|
||||
- `foundry_update_application_metadata`
|
||||
- `foundry_add_page_instance`
|
||||
- `foundry_upsert_map_pin_binding`
|
||||
- `foundry_upsert_map_data_product_binding`
|
||||
|
||||
`foundry_upsert_map_pin_binding` хранит только визуальную, provider-neutral привязку `elevated-spike`: стабильный id, subject, координаты, semantic status и ссылку на источник сущности. Поток живых данных не передаётся в MCP по одной позиции: визуальная привязка и поток данных будут связываться следующими contract/ontology слоями.
|
||||
|
||||
`foundry_upsert_map_data_product_binding` сохраняет только декларацию
|
||||
`data product → Map entity-stream slot`: versioned data product id, semantic
|
||||
types и допустимую field projection. Endpoint, provider, tenant, connection,
|
||||
token, credential и raw payload валидатор отклоняет. Page runtime получает
|
||||
scoped snapshot/patch поток через Platform, но не через Foundry MCP.
|
||||
|
||||
## Runtime data-product boundary
|
||||
|
||||
Для Map Page Foundry предоставляет только same-origin runtime routes:
|
||||
|
||||
```text
|
||||
GET /api/applications/:applicationId/pages/:pageId/data-bindings/:bindingId/snapshot
|
||||
GET /api/applications/:applicationId/pages/:pageId/data-bindings/:bindingId/stream?after=:cursor
|
||||
```
|
||||
|
||||
Маршрут разрешает persisted binding, а затем серверно находит opaque EDP reader
|
||||
grant по `sha256(applicationId/pageId/bindingId)`. Grant находится в
|
||||
root-owned read-only directory, передаётся только как `Authorization` во
|
||||
внутренний External Data Plane и никогда не попадает в browser, manifest,
|
||||
MCP или лог. В browser отдаётся только canonical data-product envelope:
|
||||
snapshot, safe `nodedc.data-product.patch/v1` upserts и cursor. `Last-Event-ID`
|
||||
авторитетнее старого `after` при автоматическом SSE reconnect.
|
||||
|
||||
## Entitlement для AI Workspace
|
||||
|
||||
`POST /api/ai-workspace/entitlements`
|
||||
|
||||
Endpoint предназначен для уже существующего AI Workspace generic adapter. Он принимает его штатный request-контракт и выдаёт динамический MCP grant только если совпала одна из политик:
|
||||
|
||||
- `FOUNDRY_ALLOW_ALL_AUTHENTICATED=true` — временно разрешает всем уже аутентифицированным пользователям;
|
||||
- `FOUNDRY_ALLOWED_OWNER_IDS` — список дополнительных разрешённых user id / owner key;
|
||||
- `FOUNDRY_ALLOWED_OWNER_GROUPS` — список дополнительных разрешённых групп.
|
||||
|
||||
По умолчанию доступ получает `user_root` (`dcctouch@gmail.com`) и техническая
|
||||
группа Authentik `nodedc:module-foundry:access`. В Launcher эта группа выдаёт
|
||||
роль сервиса `member`; все остальные пользователи получают «нет доступа» до
|
||||
явной выдачи права администратором Hub. Многопользовательское владение
|
||||
Applications в v0.1 не вводится: это общий внутренний контур с аудитом actor id.
|
||||
|
||||
Для production предпочтительны явно заданные users/groups; режим `ALLOW_ALL_AUTHENTICATED` допустим только для внутренней песочницы.
|
||||
|
||||
Штатный запрос adapter выглядит так:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": "ai-workspace.entitlement-request.v1",
|
||||
"appId": "module-foundry",
|
||||
"owner": {
|
||||
"userId": "authenticated-user-id",
|
||||
"key": "owner-or-workspace-key",
|
||||
"groups": ["optional-group"]
|
||||
},
|
||||
"activeContext": {},
|
||||
"runContext": {},
|
||||
"requestedAt": "2026-07-13T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Server-side configuration
|
||||
|
||||
Все значения находятся только в server environment. В browser bundle не передаются tokens, entitlement keys и runtime volume paths.
|
||||
|
||||
```dotenv
|
||||
FOUNDRY_PUBLIC_URL=https://<future-foundry-domain>
|
||||
FOUNDRY_MCP_URL=https://<future-foundry-domain>/api/mcp
|
||||
# Existing NODE.DC server-to-server credential; never send it to a browser or worker.
|
||||
NODEDC_INTERNAL_ACCESS_TOKEN=<existing-platform-service-value>
|
||||
FOUNDRY_MCP_CAPABILITY_TTL_MS=600000
|
||||
# Optional exceptional identities, not the default dcctouch superadmin grant.
|
||||
FOUNDRY_ALLOWED_OWNER_IDS=<comma-separated-extra-ids>
|
||||
FOUNDRY_ALLOWED_OWNER_GROUPS=<comma-separated-extra-groups>
|
||||
FOUNDRY_ALLOW_ALL_AUTHENTICATED=false
|
||||
FOUNDRY_MCP_ALLOWED_ORIGINS=https://<ai-workspace-domain>
|
||||
```
|
||||
|
||||
Foundry не требует у пользователя новый secret. `NODEDC_INTERNAL_ACCESS_TOKEN` —
|
||||
уже существующий server-to-server credential платформы: он находится только в
|
||||
server environment и используется и для Launcher/Authentiк handoff, и для
|
||||
внутренней entitlement-проверки. Worker получает лишь короткоживущую capability.
|
||||
|
||||
После появления домена в deployment AI Workspace добавляется только штатная настройка adapter; новый worker, отдельная оркестрация или специальный bridge не нужны:
|
||||
|
||||
```text
|
||||
AI_WORKSPACE_ENTITLEMENT_ADAPTERS_JSON='{"module-foundry":{"url":"https://<future-foundry-domain>/api/ai-workspace/entitlements","required":false}}'
|
||||
```
|
||||
|
||||
Если в переменной уже есть другие adapters, `module-foundry` добавляется в тот же JSON-объект — существующие `ops`, `engine`, `launcher` и другие grants не заменяются.
|
||||
|
||||
## Deployment package
|
||||
|
||||
Foundry подготовлен к отдельному deployment как stateless server + один named persistent volume. Runtime volume содержит Applications, Design Profiles, idempotency audit и media uploads. Он является единственным источником изменяемого Foundry state; Git не используется для runtime-данных и tile cache.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# использовать уже имеющийся NODEDC_INTERNAL_ACCESS_TOKEN из server environment
|
||||
docker compose --env-file .env -f infra/docker-compose.module-foundry.yml up -d --build
|
||||
curl http://127.0.0.1:9920/healthz
|
||||
```
|
||||
|
||||
Внешний маршрут подготовлен: `https://foundry.nodedc.ru` → `172.22.0.222:9920` → container `:3333`. Compose на NAS должен слушать именно `172.22.0.222:9920`, а не все интерфейсы. До запуска Foundry proxy закономерно возвращает `502`; это проверяемый признак, что DNS/TLS/proxy уже доходят до внутреннего destination. Публикация допустима только после проверки Launcher's handoff, server-side session revalidation, синхронизации Authentik-группы и выдачи Hub access. Только после этого добавляется Foundry entitlement adapter в platform AI Workspace deployment.
|
||||
|
||||
## Следующие слои
|
||||
|
||||
1. Онтологический contract приложения, страницы, page instance, Map Page и visual entities.
|
||||
2. Runtime adapter между ontology/gateway потоками и сохранёнными visual bindings.
|
||||
3. Реальные `elevated-spike`, targets, zones, routes и toolbar commands на instance Map Page.
|
||||
4. Access/Hub registration и публикация module route.
|
||||
5. Отдельная пустая программируемая страница — только после того, как готовые page templates и их MCP-контуры отработаны.
|
||||
Reference in New Issue
Block a user