# 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, provider API или произвольный внешний endpoint из 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 | | Управлять server-owned consumer только для persisted approved binding | Передавать reader capability, EDP URL или raw provider payload через MCP | Каждая запись требует `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 MCP-Protocol-Version: 2025-06-18 ``` Endpoint поддерживает два существующих контура входа, не смешивая их: - AI Workspace получает короткоживущую Foundry capability после штатной entitlement-проверки. Она подписана существующим внутренним service credential, привязана к `actorId` и `ownerKey`, живёт не более 10 минут и не раскрывает platform credential worker-у; - внешний Codex получает отдельный durable Foundry Agent credential через настройки текущего пользователя Foundry. Credential действует до явного `revoke` и не является AI Workspace capability. Заголовки с actor/owner от клиента не принимаются: пользовательский контекст извлекается только из проверенной capability или server-side записи Agent. Доступные инструменты: - `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_plan_map_data_product_consumer` - `foundry_apply_map_data_product_consumer` - `foundry_get_map_data_product_consumer_status` - `foundry_accept_map_data_product_consumer` - `foundry_rollback_map_data_product_consumer` `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. Пять consumer-инструментов управляют тем же runtime, который обслуживает Map Page. `plan` проверяет persisted binding, Data Product version/delivery, versioned policy и безопасно показывает `readerGrantAction=ensure|reuse`. Если grant отсутствует, EDP сам разрешает единственный active writer scope по Data Product id; provider/tenant/connection в Foundry не возвращаются. `apply` принимает exact `planId`, подписанно передаёт EDP только SHA-256 digest нового target-scoped token, коммитит snapshot и включает consumer; `status` возвращает только safe cursor, счётчики subject/status/reconnect и количество viewers/upstream streams; `accept` берёт bounded diagnostic lease; `rollback` останавливает consumer, но сохраняет последний safe snapshot. Capability value, grant path, internal URL и fact attributes в MCP diagnostics не возвращаются. ## 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/history?from=:iso&to=:iso&resolutionMs=:ms&sourceIds=:ids&limit=:n&cursor=:opaque GET /api/applications/:applicationId/pages/:pageId/data-bindings/:bindingId/stream?after=:cursor ``` Маршрут разрешает persisted binding, а затем server-owned consumer находит opaque EDP reader grant по `sha256(applicationId/pageId/bindingId)`. Нормальный grant создаётся внутри persistent private runtime Foundry; runner монтирует отдельный Ed25519 private key только для подписи digest-only provisioner request, а EDP получает только public trust. Старый root-owned read-only grant directory остаётся fallback для уже выданных grant. Сам token передаётся только как `Authorization` во внутренний External Data Plane и никогда не попадает в browser, manifest, MCP или лог. В browser отдаётся только safe Foundry projection канонического data-product envelope: snapshot, bounded `nodedc.data-product.history/v1`, safe `nodedc.data-product.patch/v1` upserts и cursor. History query проходит через тот же exact binding/read grant и не создаёт L2 execution на каждого viewer. `Last-Event-ID` авторитетнее старого `after` при автоматическом SSE reconnect. Consumer state хранится отдельно от application manifest в persistent runtime volume. Snapshot atomically заменяет subject projection, включая явный empty snapshot. Patch применяется только от exact previous cursor; новый cursor и semantic state сначала сохраняются, затем fan-out отправляется browsers. Повторный cursor идемпотентно игнорируется, gap вызывает snapshot rebase. Несколько viewers одного binding делят один upstream EDP stream; закрытие последнего viewer закрывает subscription, но не влияет на независимый producer в Engine L2. Freshness и remove принадлежат versioned provider-neutral policy из `registry/data-product-consumer-policies.json`. Stale вычисляется по `observedAt`; terminal statuses сохраняются. Временная transport/credential ошибка не удаляет subject. Удаление допустимо только при отсутствии в авторитетном snapshot rebase или по canonical `tombstone`/`revoked` operation. Renderer получает стабильный `sourceId + semanticType`, persisted coordinates и Foundry-owned `presentationStatus`; provider identity на стиль и lifecycle не влияет. ## Внешний Codex: Foundry + отдельная Ontology MCP В профиле Foundry раздел `Настройки → Codex Agent API` создаёт Agent текущего пользователя и выдаёт одноразовую setup-команду. Команда устанавливает в Codex две независимые MCP-конфигурации: ```text nodedc_module_foundry → POST /api/mcp nodedc_ontology → POST /api/ontology-mcp ``` Это одна операция установки, но не одна MCP. Foundry credential принимается только `/api/mcp`, Ontology credential — только `/api/ontology-mcp`; их перекрёстное использование отклоняется. Ontology route после своей проверки проксирует MCP JSON-RPC во внутренний `Ontology Core /mcp` с уже существующим server-only `NODEDC_INTERNAL_ACCESS_TOKEN`. Foundry не импортирует ontology tools в свой список и не становится маршрутизатором платформенных MCP. Agent credentials не имеют календарного срока жизни: они валидны до явного отзыва Agent. Setup code одноразовый и живёт 15 минут. Raw credentials возвращаются только в момент redeem; в Foundry runtime сохраняются только SHA-256 digests, device metadata и bounded audit. Один `revoke` закрывает обе credentials конкретного Agent. Первый срез намеренно ограничен текущим аутентифицированным пользователем: Codex может создавать и настраивать его Applications средствами Foundry MCP и читать общую Ontology через отдельную read-only MCP. Sharing, роли, межпользовательская видимость, release/publication и новые product ACL в этот срез не входят. Установщик идемпотентно обновляет только блоки `nodedc_module_foundry` и `nodedc_ontology` в `~/.codex/config.toml`, сохраняет остальные MCP (включая Engine и Ops), делает backup конфигурации и устанавливает skill `foundry-context`. Перед успешным завершением он выполняет `initialize` и `tools/list` для обеих MCP. После установки Codex Desktop нужно полностью перезапустить. ## 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:// FOUNDRY_MCP_URL=https:///api/mcp # Existing NODE.DC server-to-server credential; never send it to a browser or worker. NODEDC_INTERNAL_ACCESS_TOKEN= # Internal-only Ontology Core address; never expose the Core directly. NODEDC_ONTOLOGY_CORE_URL=http://ontology-core:8080 FOUNDRY_MCP_CAPABILITY_TTL_MS=600000 # Optional exceptional identities, not the default dcctouch superadmin grant. FOUNDRY_ALLOWED_OWNER_IDS= FOUNDRY_ALLOWED_OWNER_GROUPS= FOUNDRY_ALLOW_ALL_AUTHENTICATED=false FOUNDRY_MCP_ALLOWED_ORIGINS=https:// ``` Foundry не требует у пользователя вводить platform secret. `NODEDC_INTERNAL_ACCESS_TOKEN` — уже существующий server-to-server credential платформы: он находится только в server environment и используется для Launcher/Authentiк handoff, внутренней entitlement-проверки и server-side Ontology Core proxy. AI Workspace worker получает лишь короткоживущую capability; внешний Codex — отдельные Agent credentials, но не internal token. После появления домена в deployment AI Workspace добавляется только штатная настройка adapter; новый worker, отдельная оркестрация или специальный bridge не нужны: ```text AI_WORKSPACE_ENTITLEMENT_ADAPTERS_JSON='{"module-foundry":{"url":"https:///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, data-product consumer state/cursors, media uploads, Agent/device records, setup-code digests и Agent audit. Он является единственным источником изменяемого Foundry state; Git не используется для runtime-данных и tile cache. Raw Foundry/Ontology Agent credentials и EDP reader capabilities в volume не записываются. ```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-контуры отработаны.