feat(foundry): add managed data consumers and agent settings

This commit is contained in:
Codex
2026-07-19 15:03:54 +03:00
parent 5f583caa05
commit aac44d057f
35 changed files with 4534 additions and 243 deletions
+23
View File
@@ -134,6 +134,29 @@ Pill navigation для верхней панели и компактного п
В каноническом `ApplicationShell` шапка фиксирована. Её три оси не двигаются при открытии navigation/content и не зависят от ширины предметного контента.
## UserProfileMenu
Каноническая выпадашка профиля для правой группы `AppHeader`. Она использует
общий portal `Dropdown`, показывает identity cover с аватаром, именем и
подписью и принимает список application-owned действий.
Компонент владеет только геометрией, темизацией, закрытием по outside pointer и
Escape. Переход в профиль, открытие настроек, logout, permissions и продуктовые
правила остаются в приложении. Меню не должно содержать скрытый MCP или ACL
контракт.
## FeatureSettingsWindow
Большое модальное окно настроек по принятой механике нового Engine: identity
текущего модуля слева, сгруппированная навигация features и scrollable content
справа. Компонент переиспользует `Window`, поэтому не создаёт собственный
portal, backdrop или focus trap.
`FeatureSettingsWindow` хранит только controlled active section. Формы,
setup-команды, сохранение, API и права принадлежат приложению. Цвет и материал
берутся из активного Design Profile; отдельная Engine/Foundry тема внутри
компонента запрещена.
## ApplicationShell и ApplicationPanel
Общий шаблон приложения по механике Launcher/Hub: фиксированный `AppHeader`, центральный stage, левая navigation panel и отдельное правое content window.
+39 -5
View File
@@ -2,7 +2,9 @@
`NDC Foundry Binding` is a deploy/control-plane operation. Realtime facts do
not pass through the node: Foundry persists an approved page-slot binding and
its same-origin BFF reads snapshot + patch from External Data Plane.
its server-owned consumer reads snapshot and durable patch from External Data
Plane. The same-origin BFF serves the persisted projection and bounded history
without exposing the reader grant.
## Private workload API
@@ -40,10 +42,42 @@ No authorization claim is accepted from request headers or workflow data.
Before persisting a binding, Foundry verifies that the target-specific EDP
reader grant exists, is active, permits the selected product and exposes a
`snapshot+patch` product. The reader grant filename remains
`sha256(applicationId/pageId/bindingId)`. If it is not ready, POST fails with
`409 data_product_reader_grant_not_ready`; an unusable visual binding is never
saved.
`snapshot+patch` product. The workload API remains fail-closed: if the grant is
not ready, POST fails with `409 data_product_reader_grant_not_ready`; an
unusable visual binding is never saved.
The Foundry MCP consumer lifecycle owns managed grant creation. Its safe
`plan` asks EDP to resolve unique active writer coverage by Data Product id.
The response contains no provider, tenant or connection. Exact `apply`
generates an opaque target token inside the persistent Foundry runtime and
sends only its SHA-256 digest in a request signed by a dedicated Foundry
Ed25519 service identity. EDP persists a durable, explicitly revocable reader
binding. The signing private key is runner-managed and distinct from Engine;
EDP mounts only its public trust copy. Legacy root-owned deployment grants are
read-only compatibility fallback, not the normal lifecycle.
The canonical Map entity-stream slot is `points`. Other templates may define
their own typed slots, but the workload grant must name them explicitly.
## Server-owned consumer lifecycle
An approved binding is the only addressable consumer target. Foundry persists
consumer state under the runtime volume, keyed by application/page/binding; it
stores the binding identity, Data Product version, policy version, cursor,
snapshot generation, safe subjects, presentation status and reconnect
metadata. It never stores the reader capability value or EDP endpoint.
Foundry MCP exposes exact `plan`/`apply`, safe `status`, bounded `accept` and
non-destructive `rollback` for this target. A missing legacy consumer record is
bootstrapped lazily from the already approved binding; a missing target grant
appears as the explicit `ensure-target-scoped-reader-grant` plan effect. An
existing Map Page therefore does not require a new provider workflow, native
n8n credential or manual pin migration.
Snapshot replacement and patch application are atomic per target. Cursor is
committed before browser fan-out. Patch replay is idempotent; a gap causes
snapshot rebase. One active upstream stream is shared by every viewer of the
same target and is closed after the final viewer releases it. Temporary source
or network failure preserves subjects. Stale and removal use the versioned
provider-neutral policy registry: removal is allowed only by authoritative
snapshot absence or canonical tombstone/revocation.
+15 -7
View File
@@ -86,13 +86,21 @@ semantic types, field projection и `slotId` (`points` для live point entitie
При открытии Application page browser делает same-origin запрос к Foundry:
```text
Application/Page/Binding → Foundry BFF → External Data Plane snapshot → SSE patch
Application/Page/Binding → Foundry BFF → External Data Plane snapshot/history → SSE patch
```
Foundry сопоставляет target с root-owned opaque reader grant в закрытом
deployment directory. Browser, manifest и Cesium adapter не получают provider
Foundry сопоставляет target с opaque reader grant в закрытом persistent
runtime. При первом exact consumer apply Foundry генерирует token локально и
передаёт EDP только его digest в запросе с отдельной service-подписью; EDP сам
разрешает unique active writer scope и fail-closed отклоняет ambiguity. Legacy
root-owned deployment directory используется только как совместимый fallback.
Browser, manifest и Cesium adapter не получают provider
endpoint, tenant/connection scope, reader token или raw provider payload.
Сначала приходит snapshot, затем только patch events с cursor; при пропуске
cursor BFF требует resync snapshot. Renderer держит отдельный `CustomDataSource`
на binding и обновляет stable entity id без пересоздания viewer. History и
sampling остаются политикой Data Plane, а не Map Template.
Сначала server-owned Foundry consumer коммитит snapshot, затем применяет patch
events с exact cursor и только после commit делает fan-out всем viewers. При
пропуске cursor требуется snapshot rebase; несколько вкладок одного binding не
создают несколько upstream EDP subscriptions. Timeline использует provider-neutral
history route (`from/to/resolution/sourceIds/cursor`) через тот же BFF и не
занимает общую L2 execution queue. Renderer держит отдельный `CustomDataSource`
на binding и обновляет stable entity id без пересоздания viewer. Sampling и
retention остаются политикой Data Plane, а не Map Template.
+109 -18
View File
@@ -13,9 +13,10 @@ MCP не вводит новую оркестрацию. Доступ выдаё
| Просматривать Page Library и экземпляры Applications | Менять канонический Page Library или дизайн-компоненты |
| Создавать application instance | Удалять application instance через MCP |
| Изменять название, slug и описание instance | Создавать свободный canvas или произвольный React-интерфейс |
| Добавлять повторные instances зарегистрированной страницы | Вызывать Engine, провайдера карт или внешний API из Foundry MCP |
| Добавлять повторные 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 и повторный вызов с тем же ключом и тем же входом вернёт исходный результат без дублирования. Повтор ключа с иным входом завершается конфликтом.
@@ -28,15 +29,22 @@ MCP не вводит новую оркестрацию. Доступ выдаё
Обязательные HTTP-заголовки каждого вызова:
```http
Authorization: Bearer <short-lived server-issued Foundry capability>
Authorization: Bearer <Foundry capability or Foundry Agent credential>
MCP-Protocol-Version: 2025-06-18
```
Capability выдаётся только сервером Foundry после штатной entitlement-проверки
AI Workspace. Она подписана существующим внутренним service credential,
привязана к `actorId` и `ownerKey`, живёт не более 10 минут и не даёт worker
доступа к самому platform credential. Заголовки с actor/owner от worker не
принимаются: контекст извлекается только из подписанной capability.
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.
Доступные инструменты:
@@ -48,6 +56,11 @@ AI Workspace. Она подписана существующим внутрен
- `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 слоями.
@@ -57,23 +70,97 @@ types и допустимую field projection. Endpoint, provider, tenant, conn
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, а затем серверно находит 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`
Маршрут разрешает 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`
@@ -118,6 +205,8 @@ 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>
# 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=<comma-separated-extra-ids>
@@ -126,10 +215,12 @@ 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.
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 не нужны:
@@ -141,7 +232,7 @@ AI_WORKSPACE_ENTITLEMENT_ADAPTERS_JSON='{"module-foundry":{"url":"https://<futur
## Deployment package
Foundry подготовлен к отдельному deployment как stateless server + один named persistent volume. Runtime volume содержит Applications, Design Profiles, idempotency audit и media uploads. Он является единственным источником изменяемого Foundry state; Git не используется для runtime-данных и tile cache.
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