docs(platform): align connector, EDP, and deploy canon

This commit is contained in:
Codex 2026-07-17 18:09:55 +03:00
parent 2dd6e33a54
commit 31e078d6e5
8 changed files with 831 additions and 414 deletions

View File

@ -49,4 +49,4 @@ Ontology Core живёт в `services/ontology-core` как docs-first сема
Map Gateway живёт в `services/map-gateway` как общий platform boundary для provider credentials, безопасного asset endpoint exchange, tile/3D Tiles proxy и persistent offline TileCache. Runtime cache не является source artifact и хранится в named volume/object storage, а не в Git. Map Gateway живёт в `services/map-gateway` как общий platform boundary для provider credentials, безопасного asset endpoint exchange, tile/3D Tiles proxy и persistent offline TileCache. Runtime cache не является source artifact и хранится в named volume/object storage, а не в Git.
Внешние бизнес-поставщики подключаются по `packages/external-provider-contract`: adapter конкретного API принадлежит изолированному NDC Agent L2 workflow, а Platform даёт один provider-neutral External Data Plane для raw retention, canonical facts, current/history projections и scoped read products. Connection instance несёт tenant scope, credential reference и collection profile, но не secret value. Provider/domain mapping, entity filtering и renderer logic не попадают в Platform Data Plane; writer capability привязана к immutable EDP binding и выдаётся только отдельному Engine provisioner, не shared internal bearer. Подробный канон — `docs/ADR_L2_OWNED_EXTERNAL_CONNECTORS.md`. Внешние бизнес-поставщики подключаются по `packages/external-provider-contract`: adapter конкретного API принадлежит изолированному NDC L2 workflow, а Platform даёт один provider-neutral External Data Plane для raw retention, canonical facts, current/history projections и scoped read products. Connection instance несёт tenant scope, credential reference и collection profile, но не secret value. Provider/domain mapping, entity filtering и renderer logic не попадают в Platform Data Plane. Provider-issued credentials сохраняются в native NDC L2 Credentials; внутренний scoped publish grant генерируется и сохраняется generic control plane внутри native credential boundary, EDP получает только digest, а MCP видит только opaque reference/status. Legacy plaintext provisioning и managed digest-only ensure включаются независимо и по умолчанию закрыты. Подробный канон — `docs/ADR_L2_OWNED_EXTERNAL_CONNECTORS.md`.

View File

@ -1,274 +1,238 @@
# ADR: L2-owned external connectors and provider-neutral Data Plane # ADR: внешние коннекторы принадлежат NDC L2 workflow
Статус: **accepted**. Статус: **accepted**.
Дата: 2026-07-14. Дата: 2026-07-16.
Владелец решения: NODE.DC Platform. Владелец решения: NODE.DC Platform.
## Контекст ## Контекст
NODE.DC — платформа разработки. Подключение внешнего API не должно создавать NODE.DC состоит из двух разных слоёв:
ещё один provider-specific сервис, в котором навсегда зашиты логика клиента,
нормализация и правила отображения. Иначе каждый новый account или новый тип
объекта превращается в изменение Platform runtime.
Нужна одна повторяемая форма: Platform даёт безопасные границы, контракт - платформенный слой даёт NDC L1, NDC L2, Ontology, credentials, Data Products
хранения и выдачи данных; изолированный workflow NDC Agent L2 является и Foundry-boundaries;
адаптером конкретного API и остаётся редактируемым через предоставленный - пользовательские автоматизации собирают из этих возможностей конкретную
Codex/Engine MCP. бизнес-логику.
Это решение заменяет provider-adapter часть Новый account, provider credential, tenant, расписание или интерфейс не должны создавать
`ADR_EXTERNAL_PROVIDER_DATA_PLANE.md` и все provider-specific runtime новый platform service и не должны требовать изменения NODE.DC source. Команда
предписания `ADR_GELIOS_DATA_PLANE.md`. Они сохраняются как исторические NODE.DC подключается только для нового поставщика либо для расширения
черновики, но не являются каноном для новой реализации. подтверждённых capabilities и ontology уже поддержанного поставщика.
Gelios — первый живой источник и acceptance-кейс этого канона, а не отдельная
архитектурная ветка. Документ заменяет provider-runtime решения из
`ADR_EXTERNAL_PROVIDER_DATA_PLANE.md` и `ADR_GELIOS_DATA_PLANE.md`; те
документы остаются только историей решения.
## Решение ## Решение
### 1. Адаптер внешнего API принадлежит L2 ### 1. Пакет поставщика создаётся один раз
Один изолированный L2 workflow представляет одно connection instance и Каждый поддержанный поставщик получает один версионируемый **provider package**.
является единственным местом для: Он является знанием платформы о внешнем API и содержит:
- обращения к API поставщика через ссылку на credential из Engine; - стабильный `providerId` и версии подтверждённого API;
- выбора read-capability, cursor/pagination, rate limit, retry и расписания; - credential contract без значения секрета;
- получения raw envelope, разбиения большого ответа на native batch-операции; - каталог safe-read capabilities, pagination/rate-limit metadata и response
- семантического mapping к версии ontology и формирования canonical facts; shapes;
- idempotency key, watermark и lifecycle источника; - mappings из provider fields в версии Ontology;
- deploy/run/execution/trace через Engine MCP. - совместимые Data Products и scrubbed contract fixtures;
- явно отделённый каталог команд без активного command transport.
L2 не хранит значение provider secret или writer token: он использует только Пакет не является runtime service, scheduler, базой данных или customer
Engine-bound credential references. Plaintext writer token выдаётся trusted configuration. Он расширяется только когда фактически подтверждён новый API,
provisioner ровно один раз при create/rotate binding и затем хранится только в новый тип данных или новая ontology revision.
Engine credential store, привязанном к этому L2; External Data Plane хранит
лишь его hash. Provisioner читает отдельный runner-owned secret file, который
read-only монтируется только в EDP и позднее в dedicated Engine provisioner,
работающий под выделенным UID/GID `11006`; это не shared `.env`, не
`NODEDC_INTERNAL_ACCESS_TOKEN` и не provider credential. До deployment этого
provisioner `EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED=false`, поэтому issuance
routes fail closed. Token не попадает в L2 graph/profile, contract artifact,
execution log/trace, raw envelope, Foundry или UI. Command/write-capabilities
не получают transport route в read workflow.
Для новой учётной записи создаётся новый instance/profile этого workflow с Одна учётная запись поставщика задаётся данными, а не кодом:
другой credential reference и connection configuration. Это не требует
изменения Platform source. Если поставщик раскрывает новый объект или поле,
сначала расширяется ontology/capability contract, затем изменяется именно
соответствующий L2 workflow.
### 2. Platform Data Plane нейтрален к provider и домену `provider package + credential reference + connection profile + NDC L2 workflow instance`
Platform предоставляет один versioned **External Data Plane**. Он принимает Поэтому второй account или другая provider credential pair создаёт ещё один credential/profile и
batch по внутреннему аутентифицированному контракту, хранит raw envelope с workflow instance. Платформенный source, Data Plane и Foundry при этом не
retention/provenance, canonical facts, current/history projections и отдаёт меняются.
scoped data products/realtime updates. В нём запрещены:
- ветвления по названию provider, customer, account, vehicle или renderer; ### 2. NDC L1 проектирует, NDC L2 workflow исполняет
- provider field mapping, unit allowlist, business filtering и visual rules;
- provider token, прямой browser-to-provider доступ и command transport.
Его canonical write-форма, которую Data Plane валидирует и сохраняет после NDC L1 получает пользовательское намерение, проверяет доступные capabilities и
авторизации, содержит только нейтральные элементы: Ontology, проектирует или изменяет разрешённый NDC L2 workflow через NDC MCP и
анализирует execution evidence.
```text Один изолированный NDC L2 workflow представляет один connection instance и
source: providerId + tenantId + connectionId владеет исполняемой механикой:
contract: contractVersion + ontologyRevision + dataProductId
batch: runId + sequence + idempotencyKey + receivedAt
raw: optional restricted ref + contentType + hash + retention policy
facts: stable sourceId + semanticType + observedAt + attributes + geometry?
```
Для нового L2 writer-а это не wire-форма. Он вызывает - safe-read вызовами provider API через credential reference;
`POST /internal/data-plane/v1/data-products/:dataProductId/publish` с bearer - pagination, cursor, retry, rate limit, batch size и collection cadence;
writer token и формой `nodedc.data-product.publish/v1`, в которой вообще нет - проверкой response shape и разбиением ответа на items;
`source`, contract metadata, transport или persistence policy. Любой caller - mapping к точной ontology revision;
scope и scope headers запрещены. - idempotency, watermark и публикацией canonical facts;
- private workflow state, когда он действительно нужен.
External Data Plane разрешает token в собственный immutable writer binding Provider-specific mapping сначала реализуется штатными workflow nodes и малым
`(tenantId, connectionId, providerId, allowedDataProductIds, expiresAt)`, Code-преобразователем. Это позволяет проверить реальный API без преждевременного
проверяет active/non-expired binding, совпадение `providerId` и разрешение создания custom nodes. В custom NDC nodes переносится только повторившаяся и
`dataProductId`, затем сам materializes canonical `source` с tenant/connection доказанная **платформенная boundary-механика**, а не уникальная логика
и только после этого валидирует и сохраняет batch. Присланный caller scope поставщика.
отклоняется, а не merge/override-ится. Изменение tenant, connection, provider,
allowed data products или срока требует нового binding; для существующего
binding допустимы только token rotation и revoke.
Выпуск, rotation и revoke binding принимаются только от dedicated provisioning ### 3. Credentials являются данными connection instance
principal с runner-owned secret file; он не монтируется в shared `.env` или L2.
Shared legacy bearer не может создавать writer capability.
Идентификатор источника стабилен в пределах `(providerId, connectionId, Gelios выдаёт ровно два provider secrets: access token и refresh token. Текущий
sourceId)`. Отсутствие объекта в очередном ответе помечается lifecycle-state HTTP request binding `httpBearerAuth` использует access token. Автоматический
или временем последнего наблюдения; запись и её история не удаляются. refresh в принятом runtime не доказан, поэтому provider package фиксирует его
как `operator_managed`; ни один из token values не попадает в graph, package,
Ontology, Data Plane, Foundry, execution logs, MCP или Ops. Новый account или
provider-issued token pair означает новые native credential records и
connection instance, но не новую Platform-сущность.
### 3. Граница данных и интерфейса Локальное имя credential (например, с суффиксом `read access`) не является
provider scope. В target-контракте Read-классификацию задаёт allowlisted
method/endpoint в capability catalog и Engine workflow policy; тот же access
token нельзя называть отдельным «read token» или «write token» только из-за
label. Сейчас deployed Engine safe-ref policy ограничивает generic HTTP
credential только по host, но ещё не связывает его с package/version и exact
method/path. Поэтому Gelios `GET /api/v1/units` пока является декларативной
capability, а не завершённой runtime-security гарантией; canonical acceptance
требует capability-bound Engine policy и её MCP proof.
```text Writer capability для публикации Data Product — отдельный внутренний native NDC
Provider API (safe read) L2 credential, не третий Gelios token. Generic Engine/Platform control plane
-> Engine provider-credential reference генерирует capability внутри native credential boundary, сохраняет её там же и
-> L2 connector instance (adapter + mapping + batches) передаёт EDP только SHA-256 digest вместе с точным provider, connection и
-> Engine writer-credential reference набором Data Products. Пользователь и MCP получают только opaque
-> External Data Plane Data Product publish (scope materialized server-side) reference/status. Capability не записывается в graph, provider package, env,
-> External Data Plane (generic persistence + projections) file, Ops или trace и не копируется через пользовательский UI.
-> scoped data product / realtime stream
-> Foundry binding / user interface
```
Ontology Core описывает semantic types, связи, capability catalog и mapping Широкий credential sink, resolver/daemon с произвольной записью secret и любой
versions, но не хранит runtime payloads или секреты. Foundry получает только provider-specific credential service запрещены. Нужна одна узкая операция
approved data product и решает presentation: pins, visibility, layers and `ensure data-product publish grant`: scope выводится из granted L1→L2 target,
filters. Оно не получает provider endpoint или credential. зарегистрированного connection profile и разрешённого Data Product; issuance,
rotation и native binding должны быть idempotent, CAS/crash-safe и auditable.
Engine генерирует capability внутри credential boundary и передаёт EDP только
SHA-256 digest через idempotent binding key + generation; EDP не возвращает
plaintext. Новая generation создаётся до переключения node reference, а старая
отзывается только после успешного bind/acceptance.
Старые ручные EDP `POST/rotate` endpoints, которые возвращают capability один
раз, включаются отдельным legacy-флагом и не входят в canonical acceptance.
Digest-only managed endpoint имеет независимый флаг и принимает только
Ed25519-signed Engine service requests: exact audience/method/request target/raw
body hash входят в подпись, timestamp ограничен по skew, nonce защищён bounded
fail-closed replay cache. Legacy provisioner bearer на managed routes не
действует. EDP получает только deployment public key; matching private key
остаётся только внутри Engine server boundary. Endpoint остаётся выключенным,
пока key provisioning, Engine signer и exact native credential binding policy
не пройдут runtime acceptance.
В deployed Engine MCP этой операции пока нет — это текущий platform gap перед
canonical publish proof, а не действие пользователя. Это решение не заявляет
автоматический refresh Gelios: до отдельного runtime proof он остаётся
`operator_managed`.
### 4. Полнота без entity allowlist ### 4. External Data Plane нейтрален к поставщику
Collection profile выбирает разрешённые **read-capabilities**, а не список Platform предоставляет один versioned External Data Plane. Он принимает
конкретных объектов. Если выбранный read endpoint возвращает все доступные canonical facts через scoped writer binding, хранит current/history
источнику сущности, L2 передаёт все валидные элементы. Новые сущности projections и публикует snapshot/patch contracts. В нём запрещены:
добавляются автоматически; скрытие/отображение — задача data product/UI, а не
сбора. Технические лимиты существуют только как размер batch, backpressure,
quota и защита от повреждённого ответа, но не как бизнес-фильтр по ID.
### 5. Масштабирование и native NDC Agent nodes - ветвления по provider, customer, account, entity или renderer;
- provider field mapping и бизнес-фильтрация;
- provider credential, endpoint, schedule или command transport;
- caller-supplied tenant/connection scope.
L2 не запускает отдельный execution на каждую сущность. Он использует native `NDC Data Product Publish` отправляет только
nodes NDC Agent: HTTP Request, Split Out, Split In Batches/Loop Over Items, `nodedc.data-product.publish/v1`. External Data Plane materializes immutable
Aggregate, Postgres (когда нужен private workflow state), Set/If/Merge, scope из writer binding, проверяет разрешённый Data Product и его ontology
Schedule Trigger and Respond to Webhook. Code node допустим только как малый revision, затем сохраняет batch.
преобразователь boundary-shape, когда эквивалентной native node нет; он не
становится скрытым сервисом или provider database.
Shared telemetry/history не записывается L2 напрямую в физические таблицы EDP runtime импортирует только package subpath
Platform Postgres: это создало бы coupling к schema. Direct Postgres допустим `@nodedc/external-provider-contract/data-plane`. Его deploy artifact и image
для private state конкретного workflow или отдельной project-owned базы. содержат только provider-neutral wire validators; `providers/gelios`, mappings,
Общий контур использует только External Data Plane contract и bulk batches. fixtures и tests туда не входят. Изменение или добавление provider package не
пересобирает и не перезапускает EDP: каталог поставщиков разворачивается через
Engine/Ontology/control-plane путь отдельно.
### 5.1. Каталог custom nodes NODE.DC Collection cadence, history cadence и presentation cadence независимы:
Встроенные узлы runtime сохраняют свои штатные названия (`HTTP Request`, - NDC L2 забирает источник с частотой, нужной бизнес-задаче;
`Schedule Trigger`, `Loop Over Items` и т. п.). Любой узел, код которого - current projection принимает каждое валидное изменение;
принадлежит NODE.DC, обязан одновременно выполнять три условия: - declarative history policy может хранить все точки или sampling;
- Foundry читает snapshot+patch и отдельно ограничивает частоту render.
- видимое имя начинается с `NDC `; Для разной частоты БД и интерфейса не создаются второй provider sink, отдельный
- runtime type принадлежит package namespace `n8n-nodes-ndc.*`; gateway или параллельный прямой push в renderer.
- package-level test отклоняет публикацию узла без этого префикса и namespace.
Первый канонический набор состоит из: ### 5. Полнота означает все сущности разрешённой capability
- `NDC Data Product Publish` — runtime write boundary L2 → Data Plane; Connection profile выбирает safe-read capabilities, а не зашитый в Platform
- `NDC Data Product Read` — scoped current snapshot read по reader grant; список entity IDs. Если разрешённый endpoint возвращает все доступные credential
- `NDC Foundry Binding` — deploy/control-plane связь Data Product с сущности, NDC L2 обрабатывает каждый валидный item. Новый объект появляется
`Application → Page → typed slot`, но не транспорт каждого realtime tick. автоматически.
`NDC Foundry Output` не используется как техническое имя: оно ошибочно Пользовательская фильтрация, слои и видимость находятся после сбора — в
подразумевает прямой transport L2 → renderer. Provider-specific adapter, automation/Data Product/Foundry. Технические ограничения допустимы только как
mapping и collection profile остаются логикой конкретного L2 workflow на pagination, quota, batch size, backpressure и защита от повреждённого ответа.
штатных nodes; они не оформляются как custom NODE.DC nodes и не добавляют
provider branch в Data Plane или Foundry.
`NDC Foundry Binding` отправляет только ### 6. Custom NDC nodes ограничены платформенными границами
`nodedc.foundry.binding-upsert/v1`. Её постоянный L2 credential — отдельный
revocable `ndc_fndbg_*` workload grant с allowlist точных
`Application → Page → Binding → Slot → Data Product` targets. Короткоживущая
`fnd1.*` capability интерактивного Foundry MCP, browser session, EDP
writer/reader grant и `NODEDC_INTERNAL_ACCESS_TOKEN` для этой ноды запрещены.
Runtime node не содержит URL, endpoint, provider ID, tenant ID, connection ID, Первый канонический набор:
token, contract version или history cadence. Узел выбирает только разрешённый
Data Product; всё остальное materializes из opaque writer/reader binding и
immutable Data Product definition.
Package `n8n-nodes-ndc` устанавливается как штатный private community package в - `NDC Data Product Publish` — NDC L2 → External Data Plane;
`/home/node/.n8n/nodes/node_modules`, чтобы n8n `PackageDirectoryLoader` - `NDC Data Product Read` — scoped snapshot/patch read;
сохранил namespace `n8n-nodes-ndc.*`. `~/.n8n/custom` и - `NDC Foundry Binding` — control-plane связь Data Product с
`N8N_CUSTOM_EXTENSIONS` запрещены для этого пакета: они создают namespace `Application → Page → typed slot`.
`CUSTOM.*`. Ручное копирование или `npm install` в живом container, patch
Engine core и подмена built-in node запрещены. Production использует
проверенный offline tarball, immutable release, atomic current/previous switch,
read-only mount и restart всех n8n execution processes; acceptance завершается
только когда Engine schema и MCP catalog возвращают точные package-qualified
types.
### 5.2. Realtime delivery contract Эти nodes не содержат provider ID, tenant ID, connection ID, endpoint, token,
mapping или cadence. Runtime package сохраняет технический namespace
`n8n-nodes-ndc.*`, но пользовательские документы и интерфейсы используют
только терминологию NDC.
Новый writer использует: `NDC Foundry Binding` не транспортирует каждый realtime tick. Он создаёт
постоянную связь интерфейса с Data Product; Foundry затем читает его
snapshot+patch contract.
```text ### 7. Реальный Gelios acceptance-кейс
POST /internal/data-plane/v1/data-products/:dataProductId/publish
Authorization: Bearer <opaque writer binding>
```
Body имеет schema `nodedc.data-product.publish/v1` и содержит только batch Первая версия provider package должна доказать путь:
identity и canonical facts. Data Plane одной транзакцией:
1. проверяет idempotency; `Gelios safe read → все доступные units → map.moving_object facts →
2. обновляет current projection только более новым или изменившимся фактом; fleet.positions.current.v1 → Foundry map`
3. применяет declarative history policy (`none`, `all`, `sampled`);
4. добавляет changed facts в durable patch outbox;
5. фиксирует монотонный cursor.
Reader grant получает snapshot `nodedc.data-product.snapshot/v1`, затем Canonical product использует ontology revision
подключается к SSE stream с `after=<snapshot.cursor>` или `Last-Event-ID`. `ontology.map.moving_object.v1` и только объявленные snake_case поля. NDC L2
Каждый outbox event имеет schema `nodedc.data-product.patch/v1`. Если cursor не передаёт caller scope и не собирает собственный batch envelope вокруг
уже удалён retention policy, stream отвечает `409 resync_required`, и consumer `NDC Data Product Publish`.
повторяет snapshot. Browser не получает Data Plane token или scope: Foundry BFF
разрешает persisted binding и читает server-only reader grant.
`snapshot+patch` в v1 является **bounded product contract**. Current projection Acceptance выполняется по порядку:
одного scoped Data Product не может содержать больше 5000 entity keys
`(sourceId, semanticType)`. Snapshot возвращает целую согласованную projection
и один barrier cursor из одной `REPEATABLE READ` transaction. Query-параметр
`limit` — только защитный потолок, а не размер страницы: если полная projection
не помещается, Data Plane отвечает `413 data_product_snapshot_limit_exceeded`
и consumer не начинает patch stream с неполной базой.
Наивная pagination current snapshot по `sourceId`, offset или независимо 1. Platform выполняет generic `ensure data-product publish grant`, выпускает
полученным page cursors запрещена: изменения между страницами могут быть scoped EDP binding и атомарно сохраняет capability в native NDC L2
пропущены или продублированы относительно patch cursor. Product с ожидаемой Credentials;
cardinality выше 5000 обязан **до включения realtime** выбрать один из двух 2. NDC MCP видит только совместимый opaque writer credential reference/status;
отдельно версионируемых контрактов: 3. применить подтверждённый graph patch без legacy intake;
4. validate/preflight;
5. один успешный manual run и проверка execution/trace/Data Product;
6. только затем отдельным изменением добавить Schedule Trigger;
7. после доказанного snapshot+patch подключить Foundry binding.
- stable partitioning в несколько Data Products, где partition key является ### 8. Capacity и topology
частью definition, а каждый partition имеет собственный полный snapshot и
независимый patch cursor;
- новый query delivery contract с явно определёнными snapshot barrier,
continuation cursor, query scope и правилами перехода к patch stream.
Такой query contract не входит в `nodedc.data-product.snapshot/v1` и не может Количество одновременно активных NDC L2 проектов ограничивается фактическими
быть имитирован полем `nextPageCursor`. До его отдельного утверждения runtime и ресурсами железа и профилем нагрузки. Пока capacity проверяется оператором и не
`NDC Data Product Read` работают только с bounded products до 5000 сущностей. автоматизируется. Канон не объявляет отдельные worker/webhook generations или
high-load topology, которых ещё нет в принятом runtime.
### 6. Порядок первой реализации
1. Версионировать нейтральный intake/read contract и поднять External Data
Plane без provider-specific logic.
2. Пересобрать существующий L2 proof в native-node pipeline: safe read →
split/batch → semantic mapping → generic append → safe summary. Никаких
provider ID filters и provider gateway endpoint.
3. Выполнить manual run, проверить execution/trace, idempotency and current
projection; только затем включить Schedule Trigger.
4. Provision immutable writer binding, one-time place its token into an opaque
Engine credential, then switch the L2 to
`/data-products/:dataProductId/publish` and remove caller scope/headers and
the shared legacy credential.
5. Подключить Foundry к data product current positions. История, геозоны и
новые сущности добавляются отдельными L2 collection profiles and ontology
revisions.
## Последствия ## Последствия
- `services/<provider>-gateway` не является шаблоном для новых интеграций. - `services/<provider>-gateway` не является частью канона и не создаётся для
Существующий experimental code не расширяется и не деплоится как часть этого новых provider integrations. Существующий self-hosted Gelios Gateway и его
решения. Timescale volume остаются frozen compatibility contour, воспроизводятся из
- Data Plane может иметь собственную БД/Timescale/PostGIS implementation, но source/deploy и не изменяются новым L2 pilot без отдельного решения.
физическая схема остаётся внутренней деталью neutral service, а не API для - Новый account или provider-issued access/refresh pair — configuration change,
L2 или Foundry. а не platform release.
- Inline raw запрещён в L2 → Data Plane v1: до отдельного raw-vault допускается - Новый provider или неподдержанная capability — versioned provider/Ontology
только restricted ref/hash либо отсутствие raw envelope. Retention reference change с contract tests.
вычисляется по серверному acceptance time, а не по timestamp из batch; Data - Существующий legacy L1/SDK workflow является frozen compatibility boundary:
Plane выполняет bounded retention sweeps после готовности сервиса и по таймеру. новый L2 pilot его не редактирует, не отзывает его credential и не ставит его
- Обычный Codex Desktop получает ровно те L2 grants, которые выданы владельцем вывод условием acceptance.
connection/workflow, и может безопасно развивать adapter только в этой - Legacy `/internal/data-plane/v1/intake` остаётся выключенным migration-only
границе. route и не используется новыми NDC L2 workflows.
- `POST /internal/data-plane/v1/intake` остаётся временным migration-only route: - Команды устройствам остаются отдельным red-domain контуром с явным
он выключен по умолчанию (`EXTERNAL_DATA_PLANE_LEGACY_INTAKE_ENABLED=false`), подтверждением, scope, idempotency и аудитом.
требует `NODEDC_INTERNAL_ACCESS_TOKEN`, canonical scoped batch и совпадающие - Foundry развивается после доказанного Data Product path; provider API и
scope headers. Новый или migrated L2 не получает этот shared token и не credentials в Foundry не попадают.
использует fallback в legacy route. После миграции всех writers он удаляется.

View File

@ -28,7 +28,7 @@ The worker receives only a pairing-bound Hub URL. The Hub verifies that the pair
It intentionally does not expose filesystem paths, evidence ledgers, credentials, raw payloads, live telemetry, databases, command dispatch, workflow mutation or Studio controls. It intentionally does not expose filesystem paths, evidence ledgers, credentials, raw payloads, live telemetry, databases, command dispatch, workflow mutation or Studio controls.
## Boundary for Gelios and future data services ## Boundary for external providers and runtime data
Ontology Core describes the canonical meanings and constraints: Ontology Core describes the canonical meanings and constraints:
@ -36,15 +36,21 @@ Ontology Core describes the canonical meanings and constraints:
gelios.unit -> gelios.telemetry_snapshot -> gelios.position_fix -> map.moving_object gelios.unit -> gelios.telemetry_snapshot -> gelios.position_fix -> map.moving_object
``` ```
It does **not** serve the Gelios database or provider API. The future Gelios Gateway/Data API is a distinct capability with its own scope checks, data contract and transport. That capability may later be supplied to an AI Workspace run as another dynamic MCP server. Ontology then gives the assistant the names, relations, guardrails and allowed data-contract route; the data capability enforces access and returns live data. It does **not** serve provider runtime data or call a provider API. Provider
access and mapping belong to the granted NDC L2 workflow; shared current and
history products belong to the provider-neutral External Data Plane. A scoped
Data Product read capability may later be supplied to an AI Workspace run as a
separate dynamic MCP server. Ontology supplies meanings, relations, guardrails
and the allowed contract route; the data capability enforces access and returns
live data.
This preserves one-way responsibility: This preserves one-way responsibility:
- Ontology Core: semantics, contracts, aliases, guardrails and context advice. - Ontology Core: semantics, contracts, aliases, guardrails and context advice.
- Gelios Gateway/Data API: access-scoped telemetry and history reads. - External Data Plane: access-scoped Data Product snapshots and updates.
- Command Gateway: separate red-domain command route, explicit confirmation and audit. - Command Gateway: separate red-domain command route, explicit confirmation and audit.
- Engine L2: workflow orchestration using granted capabilities. - NDC L2 workflow: provider access, collection and mapping using granted capabilities.
- Studio: later presentation consumer, outside this implementation. - Foundry: presentation consumer, outside this implementation.
## Live catalog updates without AI Workspace rule redeploy ## Live catalog updates without AI Workspace rule redeploy

View File

@ -97,8 +97,15 @@ Engine-owned activator may select it after exact MCP schema acceptance.
The paired Engine activation is built by The paired Engine activation is built by
`build-engine-n8n-private-extension-artifact.mjs`. It deliberately does not `build-engine-n8n-private-extension-artifact.mjs`. It deliberately does not
copy the dirty Engine `docker-compose.yml` and does not build an image. Instead copy from or write generated files into the dirty Engine worktree, and it does
it installs a narrowly scoped Compose override plus a strict transition not build an image. A fresh transition id is mandatory and previously issued
ids are rejected:
```bash
node infra/deploy-runner/build-engine-n8n-private-extension-artifact.mjs 20260717-004
```
The builder emits a narrowly scoped Compose override plus a strict transition
descriptor. On apply, the runner validates the staged release again, verifies descriptor. On apply, the runner validates the staged release again, verifies
that the running n8n container and the NAS-local `2.3.2` tag resolve to the that the running n8n container and the NAS-local `2.3.2` tag resolve to the
same immutable image ID, and extracts the package into the root-owned, same immutable image ID, and extracts the package into the root-owned,
@ -110,10 +117,15 @@ read-only Engine release tree:
The override sets `N8N_USER_FOLDER=/home/node`, which is required because the The override sets `N8N_USER_FOLDER=/home/node`, which is required because the
actual Engine service runs as root while the canonical community package path actual Engine service runs as root while the canonical community package path
is below `/home/node/.n8n`. It enables loading but disables reinstall, mounts is below `/home/node/.n8n`. The live runtime contract remains the successfully
only the exact release read-only, and uses both Compose `pull_policy: never` deployed generation-003 contract and does not set `NODE_PATH`. Only the
and `docker compose up --pull never`. No registry access, lifecycle script, runner's isolated `node -e` package-loader probe temporarily initializes the
database `installed_packages` row or custom-extension loader is involved. dependency tree bundled inside the exact n8n base image; this reproduces n8n's
own loader without redefining the live Compose state. The override enables
loading but disables reinstall, mounts only the exact release read-only, and
uses both Compose `pull_policy: never` and `docker compose up --pull never`.
No registry access, lifecycle script, database `installed_packages` row or
custom-extension loader is involved.
The runner pins the exact Engine service topology observed in source and The runner pins the exact Engine service topology observed in source and
rejects an added worker/webhook generation. Only the single actual `n8n` rejects an added worker/webhook generation. Only the single actual `n8n`
@ -140,20 +152,106 @@ PYTHONDONTWRITEBYTECODE=1 \
``` ```
For `platform` artifacts, the allowlist includes the versioned Ontology Core, For `platform` artifacts, the allowlist includes the versioned Ontology Core,
the legacy Gelios experiment and the provider-neutral External Data Plane the frozen legacy Gelios compatibility service, and the provider-neutral
sources. An External Data Plane artifact builds only its image and starts External Data Plane sources. The Gelios service remains reproducible only to
`external-data-plane-postgres` plus `external-data-plane`; it never contains a protect its existing database/workflow; it is not a template for a provider
provider credential, provider endpoint, collection schedule or command integration. An External Data Plane artifact builds only its image and
transport. Database credentials remain root-owned live `.env.synology` force-recreates only `external-data-plane`; the already healthy
`external-data-plane-postgres` container and its Timescale volume are an
independent deploy prerequisite and are never selected by an EDP application
artifact. The reviewed Compose source pins the Timescale image, named volume
and target, internal database-only network, absence of database host ports,
healthy dependency, localhost-only EDP bind and the two read-only
provisioner/trust mounts in the reviewed Compose source. The runner does not
reinterpret version-dependent `docker compose config` JSON as a second deploy
schema. Its canonical enforcement remains the artifact/path allowlist plus
hard-coded build command, selected service set, runtime-secret preparation and
health acceptance. Post-apply acceptance also requires
`database=ready`; a first-rollout failure removes only the candidate EDP
container without volumes and restores the source overlay. It never contains a
provider credential, provider endpoint,
collection schedule or command
transport. Its contract payload is an exact provider-neutral runtime subset;
`providers/*`, mappings, fixtures and tests are excluded and do not trigger an
EDP rebuild. Database credentials remain root-owned live `.env.synology`
configuration and must not reuse `NODEDC_INTERNAL_ACCESS_TOKEN`. configuration and must not reuse `NODEDC_INTERNAL_ACCESS_TOKEN`.
The External Data Plane writer-provisioner credential is different: on the On the first relevant Platform apply, the root-owned runner creates
first relevant Platform apply, the root-owned runner creates
`/volume1/docker/nodedc-platform/secrets/external-data-plane-provisioner/token` `/volume1/docker/nodedc-platform/secrets/external-data-plane-provisioner/token`
atomically in a dedicated UID/GID `11006` directory (directory `0500`, token atomically in a dedicated UID/GID `11006` directory (directory `0500`, token
`0400`). It is never an `.env` value or an artifact member; Compose mounts it `0400`). It is never an `.env` value or an artifact member and is mounted
read-only only into External Data Plane and, when implemented, its dedicated read-only only into External Data Plane.
Engine provisioner running under the same restricted identity.
Manual one-time binding issuance and digest-only managed writer ensure are
independently disabled by default. The target control-plane operation generates
and stores the capability inside native NDC L2 Credentials and sends only its
digest to EDP; users and MCP consumers receive only an opaque compatible
reference/status. The runner-owned bearer above authenticates only legacy
plaintext issuance and is intentionally rejected by managed ensure/revoke.
Managed requests use a deployment Ed25519 Engine service key; EDP mounts only
the public-key trust directory read-only. On every reviewed Engine apply and on
an EDP runtime apply, this runner creates or validates one matching Ed25519 pair:
the Engine-only private key is `root:root 0400`, while the EDP trust copy is
`root:11006 0440`. Public-only crash state, key mismatch, a non-Ed25519 key,
symlinks and permissive modes fail closed. `plan` discloses both paths without
printing key material. The private key must not be broadened into an L2 graph,
MCP surface, artifact or shared-token boundary.
The reviewed Engine source candidate has dedicated server-derived MCP
plan/apply handling for the exact `ndcDataProductWriterApi` + Data Product
Publish tuple. It is separate from the generic HTTP safe-ref path and accepts no
caller-provided provider/scope/credential identity, capability, generation or
service URL. The production managed flag remains false during staging. In the
explicitly confirmed activation window it is set to true immediately before
the Platform EDP artifact; runner acceptance then requires EDP `/healthz` to
report managed provisioning `enabled`. At that point the old Engine still has
no signer mount, so the endpoint remains usable only after the separately
accepted Engine artifact. Root/UI transfer is emergency
self-hosted diagnostics only, not the user journey or acceptance
path. A provider-specific daemon remains forbidden.
Build the narrow Engine source artifact with:
```bash
node infra/deploy-runner/build-engine-data-product-publish-grant-artifact.mjs \
engine-data-product-publish-grant-YYYYMMDD-NNN
```
The builder includes exactly the pinned provider-security catalog, the
Publish-grant service, Engine Agent scope/gateway wiring, the existing n8n
adapter and the reviewed additive backend runtime overlay. It deliberately
excludes base Compose, frontend/dist, runtime data, tests, native credentials,
the NDC L2 process and the legacy generic credential-sink route/core. The
runner fixes the runtime action to `nodedc-backend` with `--no-deps` and
`--pull never`; n8n, nginx app, database services and volumes are not selected.
Acceptance requires backend health, the active immutable backend identity and
the exact additive mount inventory. Any failure restores the touched source and
recreates only the previous verified backend runtime.
Existing Engine Agents created before Publish-grant support store the original
nine scopes as the complete developer profile. They must not require a new
agent, setup command or device credential when the profile gains a server-owned
capability. Build the compatibility artifact that introduces the durable named
`full-developer` profile with:
```bash
node infra/deploy-runner/build-engine-agent-full-grant-migration-artifact.mjs \
engine-agent-full-grant-migration-YYYYMMDD-NNN
```
This follow-up slice contains exactly
`nodedc-source/server/engineAgents/store.js`. The runner pins its predecessor to
the successfully applied Publish generation, requires the installed Publish
overlay and active immutable backend, recreates only `nodedc-backend`, and
proves the exact candidate SHA, named profile and current expanded runtime
scope view. Store schema v1 is migrated atomically to v2 only when a grant
contains the complete legacy nine-scope developer bundle. From then on the
profile name is the authorization authority and its scope list is derived on
every read, so capabilities deliberately added to `full-developer` immediately
apply to existing full grants without token or store migrations. Partial grants
become `custom` and remain exact; they are never elevated. The artifact does not
touch UI, n8n, credentials, agent tokens, workflow graphs, databases or runtime
payloads.
`module-foundry` is an independent, authenticated application component. Its `module-foundry` is an independent, authenticated application component. Its
artifact contains source and compose infrastructure only; its live artifact contains source and compose infrastructure only; its live
@ -190,12 +288,17 @@ verified; it does not alter NAS routes, VPN, DNS, or Tailscale.
Install or update the root-owned live runner on Synology: Install or update the root-owned live runner on Synology:
```bash ```bash
# First verify that no deploy process is active and state/deploy.lock is absent.
sudo install -o root -g root -m 0755 \ sudo install -o root -g root -m 0755 \
/volume1/docker/nodedc-deploy/runner-install/nodedc-deploy \ /volume1/docker/nodedc-deploy/runner-install/nodedc-deploy \
/usr/local/sbin/nodedc-deploy /usr/local/sbin/nodedc-deploy
sudo /usr/local/sbin/nodedc-deploy verify-install sudo /usr/local/sbin/nodedc-deploy verify-install
``` ```
Runner promotion is a standalone admin step, never an app-overlay artifact and
never part of a running apply. A Python process already executing the old file
keeps the old code in memory; always invoke a fresh verified process afterward.
Normal service deploys must still use explicit artifacts: Normal service deploys must still use explicit artifacts:
```bash ```bash

View File

@ -202,7 +202,13 @@ TASKER_SYNC_SOURCE=1 ./infra/synology/deploy-current.sh
Если emergency-fix был сделан прямо на Synology в этих env-файлах, перенести sanitized-значение в `.env.synology.example`/docs, а секрет оставить только в live env. Если emergency-fix был сделан прямо на Synology в этих env-файлах, перенести sanitized-значение в `.env.synology.example`/docs, а секрет оставить только в live env.
### Gelios: сбор всех units доверенного подключения ### Gelios: frozen legacy compatibility contour
`gelios-postgres` и `gelios-gateway` — уже существующий self-hosted контур с
собственным persistent Timescale volume. Он остаётся полностью воспроизводимым
в source/deploy, но не развивается и не используется как шаблон для новых
провайдеров. Его удаление или миграция требуют отдельного подтверждённого плана;
обычный Platform/EDP deploy не удаляет volume и не изменяет старый workflow.
`GELIOS_UNIT_SCOPE=all` означает **все текущие и будущие units только одной уже `GELIOS_UNIT_SCOPE=all` означает **все текущие и будущие units только одной уже
настроенной пары `GELIOS_TENANT_ID` + `GELIOS_CONNECTION_ID`**. Это не wildcard настроенной пары `GELIOS_TENANT_ID` + `GELIOS_CONNECTION_ID`**. Это не wildcard
@ -210,17 +216,71 @@ TASKER_SYNC_SOURCE=1 ./infra/synology/deploy-current.sh
Пропавший из очередного ответа unit не удаляется: его stable provider ID и Пропавший из очередного ответа unit не удаляется: его stable provider ID и
`last_seen_at` сохраняются; видимость на карте — отдельная логика витрины. `last_seen_at` сохраняются; видимость на карте — отдельная логика витрины.
Перед каноническим `nodedc-deploy apply` включить политику на Synology (скрипт Перед узким legacy apply включить политику на Synology (скрипт создаёт backup и
создаёт backup и не выводит секреты): не выводит секреты):
```bash ```bash
sudo bash /volume1/docker/nodedc-deploy/inbox/prepare-gelios-all-units-env.sh sudo bash /volume1/docker/nodedc-deploy/inbox/prepare-gelios-all-units-env.sh
``` ```
Затем применить узкий Platform-артефакт, собранный с `--gateway-only`, через В plan такого Platform-артефакта должны быть только `gelios-postgres` и
`nodedc-deploy`. В его plan должны быть только `gelios-postgres` и `gelios-gateway`; общий compose в узкий архив не входит.
`gelios-gateway`; общий `docker-compose` в такой архив не входит. После apply
Gateway начинает принимать весь состав units этого подключения. ## EDP publish grant: platform-owned credential
`NDC Data Product Writer API` — историческое имя native credential type для
внутреннего scoped EDP publish grant. Это не Gelios access/refresh token и не
право записи в Gelios.
Целевой путь полностью служебный: control plane NDC по разрешённому connection
profile генерирует capability внутри native NDC L2 Credentials, передаёт EDP
только digest для scoped binding и возвращает MCP только opaque compatible
reference/status.
Пользователь не читает, не копирует и не вводит этот внутренний секрет.
EDP уже имеет localhost-only digest-only managed endpoint. Managed endpoint и
legacy plaintext provisioning имеют раздельные флаги и по умолчанию выключены.
Runner-owned provisioner bearer относится только к legacy plaintext routes и
на managed ensure/revoke намеренно не действует. Managed caller contract —
Ed25519-signed Engine service request с exact audience, method, request target,
raw-body SHA-256, timestamp и одноразовым nonce; EDP хранит только public key и
fail-closed replay cache. Ни bearer, ни signing private key нельзя открывать
workflow, MCP или переиспользовать как shared credential. В source-кандидате
Engine добавлен отдельный server-derived plan/apply для exact пары
`ndcDataProductWriterApi` + custom Publish node; он не расширяет generic HTTP
safe-ref policy и не принимает provider/scope/credential identity, capability,
generation или service URL от MCP. До отдельного Engine deploy и runtime proof
текущий deployed MCP этого контракта не имеет. Поэтому отсутствие
совместимого reference — платформенный runtime gap, а не действие пользователя.
Ручной перенос capability из отдельно включаемого legacy endpoint через root/UI допустим только как аварийная
диагностика self-hosted контура; он не является acceptance-путём и не должен
закрепляться в пользовательской автоматизации.
Compose монтирует read-only trust directory
`/volume1/docker/nodedc-platform/trust/engine-managed-provisioner` в EDP; в нём
ожидается один regular, non-symlink файл `public-key.pem` с Ed25519 SPKI public
key. Файл не должен быть group/world-writable и должен читаться runtime UID
`11006`. Private key в этот каталог, Platform artifact, `.env` или EDP
контейнер не попадает. Canonical deploy runner теперь атомарно создаёт или
проверяет matching Ed25519 pair перед relevant Engine/EDP Compose apply: private
key остаётся `root:root 0400`, public trust — `root:11006 0440`; public-only
crash state, mismatch, symlink и неверный тип ключа отклоняются до запуска.
`EXTERNAL_DATA_PLANE_MANAGED_PROVISIONING_ENABLED` остаётся `false` на staging.
В отдельно подтверждённом activation window его переводят в `true`
непосредственно перед Platform EDP artifact; runner принимает deploy только
если `/healthz` явно вернул managed status `enabled`. Старый Engine в этот
момент ещё не имеет signer mount, поэтому usable caller появляется лишь после
следующего отдельно принятого Engine artifact.
EDP application artifact пересобирает и force-recreate только
`external-data-plane`. Уже работающий `external-data-plane-postgres` и его
Timescale volume runner не выбирает и не перезапускает; healthy database —
обязательная deploy prerequisite.
Canonical service/key/audience заданы в `.env.synology.example`; wire headers и
canonical signing payload описаны в `packages/external-provider-contract/README.md`.
До появления opaque compatible reference и успешного manual publish proof
Schedule Trigger не включать. Frozen legacy workflow и его credentials этот
процесс не изменяет.
## AI Hub relay-only deploy ## AI Hub relay-only deploy

View File

@ -5,7 +5,7 @@
исполняемый connector code. `src/index.mjs` даёт dependency-free проверку исполняемый connector code. `src/index.mjs` даёт dependency-free проверку
минимальных v1 contracts; она запускается через `npm run check`. минимальных v1 contracts; она запускается через `npm run check`.
Каждый L2 connector instance должен поставлять совместимые versioned Каждый NDC L2 connector instance должен поставлять совместимые versioned
артефакты: артефакты:
```text ```text
@ -21,16 +21,47 @@ command-catalog (metadata only until a red command gateway is approved)
``` ```
`provider-manifest` — декларативный template-артефакт: provider ID, ontology `provider-manifest` — декларативный template-артефакт: provider ID, ontology
revision, L2 template version, capability catalog и data-product IDs. Он не revision, NDC L2 template version, capability catalog и data-product IDs. Он не
является tenant connection, не содержит endpoint/URL, credential reference, является tenant connection, не содержит endpoint/URL, credential reference,
secret, ручной scope или executable provider code. Concrete non-secret secret, ручной scope или executable provider code. Concrete non-secret
connection profile принадлежит и версионируется вместе с конкретным L2 connection profile принадлежит и версионируется вместе с конкретным NDC L2
workflow; Engine только привязывает к нему opaque credential reference и grant. workflow; caller привязывает только opaque provider credential reference, а
внутренний workload grant разрешает control plane.
## Versioned provider packages
`providers/<provider>/<major>` — устанавливаемая contract/data единица, а не
runtime service и не custom node. Она объединяет manifest, auth-mode metadata,
capability catalog, field policies, collection profiles, Data Product
definitions, semantic mapping contracts, provider-neutral NDC L2 template
descriptor и synthetic fixtures. Package не содержит tenant/account state,
credential values или исполняемый provider daemon.
`instantiateL2Connection` создаёт произвольное число connection instances из
одного immutable package. Каждый instance получает собственные tenant,
connection и opaque provider credential reference, но не требует изменения
Platform source. Внутренние workload bindings объявляются отдельно как
`control_plane_managed` и не являются caller input. Канонический collection
scope — `all_visible_to_credential`: выбранная
read-capability передаёт все entities, которые provider возвращает для
привязанного credential. Unit allowlist и provider-group filter на collection
boundary запрещены; сортировка и видимость принадлежат Data Product consumer и
Foundry.
Первый production-shaped package — `providers/gelios/v1`. Он фиксирует реальный
Gelios REST `GET /api/v1/units` transport contract и точный output
`fleet.positions.current.v1@1.0.0` с revision
`ontology.map.moving_object.v1`. Все Data Product fields используют snake_case.
Его optional `tokenLifecycle` точно описывает два provider-issued artifacts —
`access` и `refresh`: request использует `access`, а refresh пока имеет режим
`operator_managed`. Это metadata без secret values и без заявления о
реализованном автоматическом refresh.
## Проверяемые v1 contracts ## Проверяемые v1 contracts
- `Connection` — provider instance, tenant scope и *ссылка* на credential в - `Connection` — provider instance, tenant scope и *ссылка* на provider
Engine. Любые token/secret/password-like поля запрещены. credential в NDC L2 Credentials. Любые token/secret/password-like поля
запрещены; publisher представлен system-managed declaration/status без ref.
- `Collection Profile` — явная policy сбора. `manual` не может скрыто содержать - `Collection Profile` — явная policy сбора. `manual` не может скрыто содержать
polling interval; `realtime` требует interval не чаще одного раза в секунду. polling interval; `realtime` требует interval не чаще одного раза в секунду.
- `Data Product` — нормализованный versioned output с semantic types, полями и - `Data Product` — нормализованный versioned output с semantic types, полями и
@ -38,18 +69,18 @@ workflow; Engine только привязывает к нему opaque credenti
- `Intake Batch` — canonical **scoped** record, который External Data Plane - `Intake Batch` — canonical **scoped** record, который External Data Plane
валидирует и сохраняет: source, contract revision, idempotency, restricted валидирует и сохраняет: source, contract revision, idempotency, restricted
raw envelope и canonical facts. Его `source` содержит `providerId`, raw envelope и canonical facts. Его `source` содержит `providerId`,
`tenantId` и `connectionId`. Writer-bound L2 request намеренно не является `tenantId` и `connectionId`. Writer-bound NDC L2 request намеренно не является
готовым `Intake Batch`: Data Plane сначала materializes scope и лишь затем готовым `Intake Batch`: Data Plane сначала materializes scope и лишь затем
применяет этот contract. В canonical record нет provider field mapping, применяет этот contract. В canonical record нет provider field mapping,
entity allowlist, token или renderer data. Inline `raw.payload` в v1 entity allowlist, token или renderer data. Inline `raw.payload` в v1
запрещён: если нужна provenance-ссылка, connector передаёт restricted запрещён: если нужна provenance-ссылка, connector передаёт restricted
`raw.ref` вместе с hash. Отдельный raw-vault может быть добавлен только `raw.ref` вместе с hash. Отдельный raw-vault может быть добавлен только
отдельным ADR и не становится частью L2 → Data Plane wire boundary. отдельным ADR и не становится частью NDC L2 → Data Plane wire boundary.
- `NDC Foundry Binding` — адресует data product только в конкретную цепочку - `NDC Foundry Binding` — адресует data product только в конкретную цепочку
`Foundry Application → Page → approved slot`; `templateId` можно сохранить `Foundry Application → Page → approved slot`; `templateId` можно сохранить
как дополнительную типизацию, но он не заменяет `applicationId` и `pageId`. как дополнительную типизацию, но он не заменяет `applicationId` и `pageId`.
В binding запрещены provider transport, endpoint и credential reference. В binding запрещены provider transport, endpoint и credential reference.
- `Provider Manifest` — статическое описание L2 connector template, capability - `Provider Manifest` — статическое описание NDC L2 connector template, capability
catalog и ontology/data-product contracts. Оно не может содержать tenant, catalog и ontology/data-product contracts. Оно не может содержать tenant,
connection, credential, secret или provider transport. connection, credential, secret или provider transport.
@ -94,13 +125,14 @@ PostgreSQL `integer` `0..2147483647`. Эти ограничения нельзя
Manifest, connection/collection profiles, data-product definition и Foundry Manifest, connection/collection profiles, data-product definition и Foundry
binding используют fail-closed allowed-key schemas. Неизвестные поля, а также binding используют fail-closed allowed-key schemas. Неизвестные поля, а также
secret-like имена или значения (включая `ndc_edpwb_`/`ndc_edprb_`) отклоняются secret-like имена или значения (включая `ndc_edpwb_`, `ndc_edprb_` и
`ndc_edppr_`) отклоняются
на общей границе. на общей границе.
Все private custom nodes NODE.DC поставляются package Все private custom nodes NODE.DC поставляются package
`platform/packages/n8n-nodes-ndc`. Их display name обязан начинаться с `NDC `, `platform/packages/n8n-nodes-ndc`. Их display name обязан начинаться с `NDC `,
а runtime type — с `n8n-nodes-ndc.`; package называется строго а runtime type — с `n8n-nodes-ndc.`; package называется строго
`n8n-nodes-ndc`. Provider-specific adapters остаются L2 workflow logic и не `n8n-nodes-ndc`. Provider-specific adapters остаются NDC L2 workflow logic и не
становятся custom nodes или ветками Data Plane. Эти инварианты проверяются становятся custom nodes или ветками Data Plane. Эти инварианты проверяются
package test. package test.
@ -110,76 +142,72 @@ declarative provider artifact и не runtime transport: команда соде
application/page/binding/data-product projection и idempotency key, а право на application/page/binding/data-product projection и idempotency key, а право на
операцию извлекается Foundry из отдельного opaque workload grant. операцию извлекается Foundry из отдельного opaque workload grant.
## Engine opaque credential sink ## Native NDC L2 Credentials
`src/engine-credential-sink.mjs` задаёт dependency-free server-to-server v1 Provider auth и внутренние workload capabilities используют один общий
границу для доставки трёх workload capabilities в Engine Credentials: секретный boundary — native Credentials NDC L2, — но являются разными
credential domains. После сохранения ядро владеет secret, а graph и MCP
используют только opaque reference.
- `external-data-plane.writer` → точная нода Gelios выдаёт ровно access token и refresh token. Текущий credential type
`n8n-nodes-ndc.ndcDataProductPublish` / `ndcDataProductWriterApi`; `httpBearerAuth` использует access token для HTTP request. Автоматический обмен
- `external-data-plane.reader` → точная нода refresh → access в текущем runtime не доказан и поэтому не заявлен: package
`n8n-nodes-ndc.ndcDataProductRead` / `ndcDataProductReaderApi`; фиксирует `refreshMode: operator_managed`. Название credential с текстом вроде
- `foundry.binding` → точная нода `read access` является лишь локальной меткой; read-классификацию задают
`n8n-nodes-ndc.ndcFoundryBinding` / `ndcFoundryBindingApi`. разрешённые endpoint/method в capability catalog и workflow policy, а не scope
самого access token. В deployed Engine exact method/path policy ещё не
подключена: текущая HTTP safe-ref граница проверяет host. Поэтому catalog
classification остаётся декларативной до capability-bound Engine/MCP proof.
Provision request фиксирует `workflowId`, `workflowRevision`, `nodeId`, runtime Для общего Data Product transport используются `ndcDataProductWriterApi`,
node type, credential type, grant ID, expiry и issuer policy hash. Aggregate `ndcDataProductReaderApi` и `ndcFoundryBindingApi`. Это внутренние NDC
`transaction.policyHash` детерминированно считается по всему secret-free capabilities, не Gelios tokens и не часть Gelios token lifecycle. Их значения
descriptor; подмена любой цели или policy ломает валидацию. Единственное поле, не являются частью provider package и не передаются между workflow nodes как
которое переносит plaintext capability, — `bindings[].material.value`; request данные.
нельзя писать в логи, traces, очередь или audit.
Provision и rollback envelopes действуют не более 15 минут: sink отклоняет Connection caller передаёт только provider credential reference. Publisher
истёкшие запросы и допускает максимум 60 секунд положительного clock skew. role остаётся credential binding для `NDC Data Product Publish`, но template
Receipt повторно проверяет freshness относительно собственного `processedAt`, маркирует его `management: control_plane_managed`: ни writer secret, ни writer
чтобы старый запрос нельзя было применить или подтвердить через replay. reference не входят в connection parameters. Instantiation возвращает
декларативный `systemBindings.publisher` с desired state и unresolved status;
дальше его разрешает trusted control plane.
Каждый binding содержит `capabilityDigest = sha256(material.value)`: Engine Canonical managed EDP writer capability генерируется и сохраняется внутри
самостоятельно хеширует полученный plaintext и сравнивает digest. Aggregate Engine credential boundary, а EDP получает только digest. Старые manual
`policyHash` включает этот digest и issuer identity, а provision transaction writer/reader и Foundry issuance paths могут возвращать capability один раз
несёт обязательную Ed25519 attestation. Engine принимает её только по trusted control-plane caller и остаются отдельно закрытыми compatibility
allowlisted `serviceId:keyId`; заменить capability и пересчитать обычный hash границами; MCP и пользователь получают только opaque reference/status.
без приватного issuer key невозможно. Запрещены password input
как пользовательский acceptance-путь, graph parameters, connection/profile
files, env, Ops, logs и traces.
Sink обязан выполнять `rollback-all`: сначала проверить весь request и точное Broad credential sink/resolver/daemon и provider-specific provisioning
состояние graph, затем создать credentials в staging, атомарно привязать весь запрещены. Требуется узкий generic `ensure data-product publish grant` contract
набор и только после commit вернуть opaque `credentialRef`. При любой ошибке с server-derived scope, idempotency, CAS/crash recovery и audit. Текущий
новые credentials удаляются, а прежние bindings остаются без изменений. deployed Engine MCP этой операции ещё не имеет; root/UI transfer допустим
`rollback-failed` означает карантин и ручное восстановление, но никогда не только как emergency self-hosted diagnostics, не как user journey.
возвращает частичные credential refs.
Receipt, rollback request/receipt и audit имеют отдельные strict schemas. Они ## NDC L2 private-extension management
не способны вернуть capability material; audit хранит только hash opaque
credential reference. Explicit rollback адресует committed transaction через
`transactionId`, `policyHash` и hash committed receipt, поэтому не может
случайно откатить другой набор. Реализация sink принадлежит Engine и не даёт
Platform/Codex доступа к Engine core, runtime files или plaintext credential
storage.
## Engine private-extension management
`src/engine-private-extension.mjs` задаёт строгую Platform-side границу для `src/engine-private-extension.mjs` задаёт строгую Platform-side границу для
Engine-owned активации проверенного `n8n-nodes-ndc` release. Контракт не NDC L2-owned активации проверенного `n8n-nodes-ndc` release. Контракт не
устанавливает package и не меняет Engine: он фиксирует async устанавливает package и не меняет NDC L2: он фиксирует async
`plan -> apply receipt -> operation/status` protocol, где `apply` обязан `plan -> apply receipt -> operation/status` protocol, где `apply` обязан
быстро вернуть `queued` и `operationId`, а долгий recreate/acceptance быстро вернуть `queued` и `operationId`, а долгий recreate/acceptance
отслеживается отдельно. отслеживается отдельно.
Активация и rollback требуют отдельной глобальной capability Активация и rollback требуют отдельной глобальной capability
`engine.private-extension.manage`. Обычные L1/L2 grants её не дают. Чтение `engine.private-extension.manage`. Обычные NDC L1/NDC L2 grants её не дают. Чтение
состояния допускает `engine.private-extension.read` или manage-capability. состояния допускает `engine.private-extension.read` или manage-capability.
Запрос выбирает только allowlisted package, digest-bound `releaseId` и Запрос выбирает только allowlisted package, digest-bound `releaseId` и
`packageSha256`; caller не передаёт host path, package bytes, Compose service, `packageSha256`; caller не передаёт host path, package bytes, Compose service,
shell command или credential material. Plan живёт не более 15 минут, является shell command или credential material. Plan живёт не более 15 минут, является
single-use и применяется только с тем же idempotency key и plan hash. single-use и применяется только с тем же idempotency key и plan hash.
Runtime transition для n8n 2.3.2 зафиксирован как community-package loader по Runtime transition использует platform-managed community-package loader для
`/home/node/.n8n/nodes/node_modules/n8n-nodes-ndc`, а не как immutable `n8n-nodes-ndc` release; private runtime paths и environment settings
`N8N_CUSTOM_EXTENSIONS` или `CUSTOM.*` loader: не являются частью публичного provider contract:
- `N8N_COMMUNITY_PACKAGES_ENABLED=true`,
`N8N_COMMUNITY_PACKAGES_PREVENT_LOADING=false`,
`N8N_REINSTALL_MISSING_PACKAGES=false`;
- Deploy/Run quiesced and execution queue drained before the version switch; - Deploy/Run quiesced and execution queue drained before the version switch;
- read-only mount and atomic current/recovery state; - read-only mount and atomic current/recovery state;
- force-recreate main, every worker and every webhook instance as one version - force-recreate main, every worker and every webhook instance as one version
@ -189,24 +217,25 @@ Runtime transition для n8n 2.3.2 зафиксирован как community-pa
Любая ошибка после switch запускает automatic rollback и повторный Любая ошибка после switch запускает automatic rollback и повторный
force-recreate/acceptance. Ошибка самого rollback переводит runtime в force-recreate/acceptance. Ошибка самого rollback переводит runtime в
`quarantined`. Immutable release и существующие Engine Credentials `quarantined`. Immutable release и существующие NDC L2 Credentials
сохраняются. Для первой активации предыдущим проверенным состоянием является сохраняются. Для первой активации предыдущим проверенным состоянием является
`n8n-nodes-ndc.inactive/v1`: rollback в этот baseline удаляет package из `n8n-nodes-ndc.inactive/v1`: rollback в этот baseline удаляет package из
loader surface, но не удаляет credentials. loader surface, но не удаляет credentials.
Public Ops gateway уже умеет прозрачно передавать эти операции через Public Ops gateway уже умеет прозрачно передавать эти операции через
`/engine/mcp`, если Engine реализует соответствующие MCP tools. Так как `/engine/mcp`, если NDC L2 реализует соответствующие MCP tools. Так как
gateway имеет 30-second upstream timeout, side effect остаётся асинхронным; gateway имеет 30-second upstream timeout, side effect остаётся асинхронным;
отдельный public REST proxy для management boundary не требуется. отдельный public REST proxy для management boundary не требуется.
Пример `examples/gelios-positions-current.v1.mjs` — provider-specific fixture `providers/gelios/v1/fixtures` содержит synthetic provider response и ожидаемый
без customer, tenant identity или credential material. publish contract без customer, tenant identity или credential material.
## Ownership ## Ownership
- `platform/packages/external-provider-contract` — общий контракт и schemas. - `platform/packages/external-provider-contract` — общий контракт и schemas.
- NDC Agent L2 — provider API adapter: fetch, pagination, batching, - NDC L2 — provider API adapter: fetch, pagination, batching,
semantic mapping, collection profile и ссылка на credential в Engine. semantic mapping, collection profile и ссылка на credential в NDC L2
Credentials.
- Platform External Data Plane — provider-neutral intake, raw retention, - Platform External Data Plane — provider-neutral intake, raw retention,
canonical facts, current/history projections и scoped read products. Он не canonical facts, current/history projections и scoped read products. Он не
знает provider fields, customer/business filters или renderer rules. знает provider fields, customer/business filters или renderer rules.
@ -222,46 +251,100 @@ gateway имеет 30-second upstream timeout, side effect остаётся ас
## Mandatory connection boundary ## Mandatory connection boundary
`connection` принадлежит одному tenant/client context и содержит только `connection` принадлежит одному tenant/client context и содержит только
ссылку на секрет, утверждённый capability scope, collection profile, field ссылку на provider secret, утверждённый capability scope, collection profile,
policy и retention policy. Во всех runtime records Data Plane сохраняет минимум field policy и retention policy. Во всех runtime records Data Plane сохраняет минимум
`tenant_id`, `connection_id`, `provider_id`, `observed_at`, `received_at` и `tenant_id`, `connection_id`, `provider_id`, `observed_at`, `received_at` и
provenance/version там, где это применимо. provenance/version там, где это применимо.
Writer token — отдельный EDP runtime credential, а не поле `Connection`, Provider credential reference текущего Gelios request адресует access token;
profile, provider manifest или L2 graph. наличие provider-issued refresh token описывается `tokenLifecycle`, но его value
не входит в `Connection`. Writer capability — отдельный внутренний EDP runtime
credential, а не Gelios token и не caller-supplied поле `Connection`, profile,
provider manifest или NDC L2 graph. Connection artifact содержит только
system-managed publisher declaration/status без credential reference.
## Scoped writer binding ## Scoped writer binding
Writer binding — EDP-owned runtime security state, а не versioned artifact Writer binding — EDP-owned runtime security state, а не versioned artifact
provider manifest или connection profile. Он фиксирует `tenantId`, provider manifest или connection profile. Он фиксирует `tenantId`,
`connectionId`, `providerId`, `allowedDataProductIds`, active/revoked state и `connectionId`, `providerId`, `allowedDataProductIds`, active/revoked state и
`expiresAt`. L2 не может редактировать binding или задавать его scope; `expiresAt`. NDC L2 не может редактировать binding или задавать его scope;
изменение любого scope-поля либо TTL создаёт новый binding, а прежний binding изменение любого scope-поля либо TTL создаёт новый binding, а прежний binding
можно только rotate/revoke. можно только rotate/revoke.
Новый L2 вызывает `POST /internal/data-plane/v1/intake/writer-bound` с Binding создаёт, сохраняет и привязывает только trusted control plane. Caller
`Authorization: Bearer <writer-token>` и unscoped envelope. В `source` connection instance не принимает `writerCredentialRef`; publisher role в L2
разрешён только `providerId`; `tenantId`, `connectionId` и template является обязательным `control_plane_managed` binding requirement.
`x-nodedc-*-id` headers запрещены. EDP проверяет token, binding, provider и
`contract.dataProductId`, затем materializes immutable canonical scope и
сохраняет обычный `Intake Batch`. Caller-provided scope отклоняется, а не
доверяется и не объединяется с binding.
Plaintext writer token возвращается только trusted provisioner при создании Новый NDC L2 вызывает
или rotation. Provisioner помещает его непосредственно в opaque Engine `POST /internal/data-plane/v1/data-products/:dataProductId/publish` с opaque
credential, доступный назначенному L2. EDP хранит только hash token и binding writer capability и envelope `nodedc.data-product.publish/v1`. В body есть
metadata; token запрещён в connection/profile/manifest, L2 graph, logs/traces, только batch identity и canonical facts; `source`, provider, tenant,
raw payload, Foundry и UI. connection, ontology revision, product version и persistence policy запрещены.
EDP проверяет binding и path `dataProductId`, materializes immutable scope и
contract из server-owned registries, затем сохраняет canonical batch.
Caller-provided scope отклоняется, а не доверяется и не объединяется с
binding.
Provisioning routes принимают только отдельный secret file, созданный Plaintext writer capability запрещена в provider package, connection/profile,
root-owned deploy runner: NDC L2 graph, files, environment, logs/traces, raw payload, Foundry и MCP.
`/volume1/docker/nodedc-platform/secrets/external-data-plane-provisioner/token`. В canonical пути она генерируется и сохраняется внутри native Engine credential
Он read-only монтируется только в EDP и позднее — в dedicated Engine boundary; EDP получает только SHA-256 digest, а workflow — только opaque
provisioner, работающий под выделенным UID/GID `11006`; это не `.env` value, reference/status. Старые ручные EDP create/rotate endpoints с one-time
не `NODEDC_INTERNAL_ACCESS_TOKEN` и не provider credential. Пока generic Engine plaintext response остаются отдельно выключенным compatibility-механизмом и не
provisioner не развёрнут, `EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED=false`, а являются пользовательским или acceptance-путём.
create/rotate/revoke routes отвечают `503` и не делают fallback к shared
internal bearer. Legacy manual one-time issuance использует отдельный EDP-only runner-managed
bearer. Digest-only managed ensure этот bearer не принимает: Engine подписывает
каждый control-plane запрос отдельным Ed25519 service key, EDP хранит только
публичный ключ. Оба пути имеют независимые флаги и по умолчанию выключены.
Generic `ensure data-product publish grant` генерирует и сохраняет capability в
native Engine credential boundary, передаёт EDP её digest и возвращает только
opaque ref/status. Shared internal bearer и Gelios access/refresh token для
этого запрещены. Deployed Engine MCP умеет читать
opaque refs и bind только HTTP Request credentials; native
`ndcDataProductWriterApi` к custom Publish node этим policy не привязывается.
Поэтому ensure + exact private-node bind остаются реальным platform gap.
Root/UI перенос — только аварийная диагностика.
Control-plane EDP boundary — idempotent
`PUT /internal/data-plane/v1/writer-bindings/by-key/:bindingKey`. Engine создаёт
capability внутри native credential boundary и отправляет только её SHA-256
`capabilityDigest`, immutable scope, `generation` и expiry. Одинаковый
`bindingKey + generation + request hash` возвращает тот же binding без нового
secret; несовпадающий retry получает `409`. Rotation использует новую
generation/credential, поэтому старый binding можно оставить активным до
успешного node bind и затем явно revoke.
Managed revoke выполняется идемпотентно по точным `bindingKey + generation`
через managed-only route; для него не открывается legacy plaintext API.
Managed route включается только через
`EXTERNAL_DATA_PLANE_MANAGED_PROVISIONING_ENABLED`; legacy plaintext routes —
через отдельный `EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED`.
Managed auth wire contract —
`nodedc.external-data-plane.managed-provisioner-request/v1`. Engine отправляет
ровно по одному header:
`x-nodedc-engine-service-id`, `x-nodedc-engine-key-id`,
`x-nodedc-request-audience`, `x-nodedc-request-timestamp`,
`x-nodedc-request-nonce`, `x-nodedc-content-sha256` и
`x-nodedc-request-signature`. Timestamp — canonical UTC ISO с миллисекундами,
nonce и signature — unpadded canonical base64url, body hash — lowercase hex
SHA-256 от точных переданных bytes. Подписываются UTF-8 bytes результата
`JSON.stringify` объекта с полями строго в порядке `schemaVersion`, `audience`,
`serviceId`, `keyId`, `method`, `path`, `timestamp`, `nonce`, `bodySha256`.
`method` и request target `path` (включая query) должны совпадать побайтно.
Canonical identity: service `nodedc-engine`, key
`engine-edp-managed-provisioner-v1`, audience
`nodedc-external-data-plane.managed-provisioning.v1`. EDP проверяет bounded
clock skew и одноразовый nonce в bounded fail-closed replay cache; bearer
fallback для managed routes отсутствует, любой `Authorization` header на них
отклоняется.
EDP импортирует только `@nodedc/external-provider-contract/data-plane`.
Artifact/image allowlist для этого subpath содержит contract version, intake,
publish/snapshot/patch validators и scrub policy; provider packages, mappings,
fixtures и tests физически не входят в EDP runtime и не являются причиной его
restart.
## Legacy intake migration ## Legacy intake migration
@ -271,22 +354,22 @@ existing writers. Он принимает только canonical scoped `Intake
`x-nodedc-tenant-id` и `x-nodedc-connection-id`. Writer token этот route не `x-nodedc-tenant-id` и `x-nodedc-connection-id`. Writer token этот route не
заменяет. заменяет.
Для миграции: создать writer binding, один раз записать выданный token в Engine Для миграции: Platform обеспечивает scoped writer binding и native credential,
credential, переключить L2 на `/intake/writer-bound`, удалить tenant/connection затем через Engine MCP новый workflow branch переключается на Data Product
из body и headers, проверить успешный intake, затем удалить у L2 legacy publish route, удаляет tenant/connection из body и headers и проверяет
credential. Новый или migrated writer не должен fallback-иться на legacy route успешный publish. Существующий legacy workflow/credential остаётся неизменным,
после ошибки writer-bound intake. Когда migrated все writers, legacy route и пока владелец отдельно не решит его вывести; новый writer не должен
его shared-token access удаляются. fallback-иться на legacy route после ошибки publish.
## Intake boundary ## Intake boundary
L2 отправляет в общий Data Plane `Intake Batch`, а не SQL-запрос в общие NDC L2 отправляет в общий Data Plane publish request, а не SQL-запрос в общие
таблицы. Platform проверяет boundary и сохраняет raw/history/current таблицы. Platform проверяет boundary и сохраняет raw/history/current
projections; semantic mapping остаётся в L2. Список конкретных source IDs не projections; semantic mapping остаётся в NDC L2. Список конкретных source IDs не
является частью connection scope: scope выбирает read-capability API, а является частью connection scope: scope выбирает read-capability API, а
visibility решает data-product consumer. visibility решает data-product consumer.
Inline `raw.payload` запрещён: L2 не может записать в Data Plane полный ответ Inline `raw.payload` запрещён: NDC L2 не может записать в Data Plane полный ответ
provider-а или произвольную строку. Пока отдельный raw-vault не утверждён, provider-а или произвольную строку. Пока отдельный raw-vault не утверждён,
batch либо не содержит `raw`, либо содержит только restricted `raw.ref` и hash. batch либо не содержит `raw`, либо содержит только restricted `raw.ref` и hash.
Secret-like keys и распознаваемые bearer/JWT/writer-token values в любом участке Secret-like keys и распознаваемые bearer/JWT/writer-token values в любом участке
@ -299,4 +382,6 @@ service делает sweep expired envelopes при старте и по рас
Capabilities классифицируются как `read`, `metadata`, `write`, `destructive` Capabilities классифицируются как `read`, `metadata`, `write`, `destructive`
или `unknown`. Только `read` и согласованные `metadata` могут попасть в или `unknown`. Только `read` и согласованные `metadata` могут попасть в
collector. `write` и `destructive` остаются каталогизированными, но не имеют collector. `write` и `destructive` остаются каталогизированными, но не имеют
transport route в read adapter. transport route в read adapter. Эта классификация относится к разрешённому
method/endpoint workflow, а не утверждает, что provider access token имеет
read-only scope.

View File

@ -0,0 +1,105 @@
# Gelios provider package v1
Это versioned contract/data package для первого живого provider connection в
NODE.DC. Он не является runtime service, custom node, account configuration или
хранилищем telemetry. Provider-specific request и mapping исполняются внутри
изолированного NDC L2 workflow; Platform получает только provider-neutral Data
Product.
Package фиксирует:
- manifest `gelios.provider.v1@1.0.0`;
- точный provider token lifecycle: Gelios выдаёт `access` и `refresh`, а
HTTP request использует `access`;
- safe-read capability `gelios.units.current.read`
(`GET https://api.geliospro.com/api/v1/units` with an opaque bearer
credential);
- dynamic scope `all_visible_to_credential` без unit/group allowlist;
- realtime profile с интервалом 10 секунд и manual profile;
- fail-closed field policy: неизвестные и dynamic fields отбрасываются до
классификации, hardware IDs, phone/address, raw params и sensor payloads не
публикуются;
- mapping в `map.moving_object`;
- стабильный source ID `gelios-unit-<sourceUnitId>` с точным сохранением
строкового provider ID;
- explicit derivation rules для `position_valid`, `operational_status` и
`quality_flags`, включая `no_position` и stale threshold 60 секунд;
- точный Data Product `fleet.positions.current.v1@1.0.0`, revision
`ontology.map.moving_object.v1`, с 13 разрешёнными snake_case fields;
- provider-neutral NDC L2 template descriptor из пяти boundary steps:
trigger → request → extract → map → `NDC Data Product Publish`;
- publish credential binding с `management: control_plane_managed`, без
caller-supplied writer reference;
- synthetic source/publish fixture, включая видимый объект без координат.
## Authentication boundary
Gelios выдаёт ровно два provider secret artifact: access token и refresh
token. Это не отдельные read/write tokens. Текущий NDC L2 HTTP binding типа
`httpBearerAuth` подставляет access token в `Authorization`; автоматический
refresh этим runtime пока не доказан, поэтому package явно фиксирует
`refreshMode: operator_managed`. Ни access, ни refresh value не попадает в
package, graph, fixture, MCP, Ops или trace.
Название native credential, например `Gelios — Robot2B — read access`, является
только операторской меткой. `read` в `gelios.units.current.read`
классификация разрешённого workflow endpoint `GET /api/v1/units`, а не scope
access token. Метка credential не создаёт отдельный «read token» и не сужает
права, которые фактически выдал Gelios.
Unit без last message не исчезает: mapping использует collection receive time
как `observedAt` fallback и публикует subject без geometry с
`position_valid=false`/`operational_status=no_position`.
Префикс source ID для v1 — строго `gelios-unit-`. Сокращённый `unit-` не
является каноническим: он создал бы второй entity key рядом с уже сохранённым
Gelios subject. Значение provider ID после префикса преобразуется только в
строку; ведущие нули и остальные значимые символы не нормализуются.
`fleet.positions.current.v1` ограничен 5000 current entities. Если credential
видит больше, NDC L2 не должен отбрасывать «лишние» units: package/profile
требует остановить Deploy до новой partitioned Data Product revision. Полное
автоматическое enforcement этого правила остаётся gate будущего
package-to-graph compiler/runtime validator.
Один package обслуживает любое число accounts. Для каждого account вызывается
`instantiateL2Connection` с новым `tenantId`, `connectionId`, provider
credential reference. Это единственная credential reference, которую передаёт
caller: она адресует Gelios access binding в native NDC L2 Credentials.
Внутреннюю capability для публикации в External Data Plane выдаёт и сохраняет
control plane; caller не передаёт writer secret или `writerCredentialRef`.
Результат instantiation содержит только provider ref и декларативный
`systemBindings.publisher` со статусом `unresolved`, который Platform должна
разрешить в native credential binding. Эта capability не выдаётся Gelios, не
является третьим Gelios token и не описывает права provider account. Добавление
account не меняет Platform source.
Расширение Gelios происходит новой версией package: сначала подтверждаются
provider capability/fields, затем обновляются ontology, field policy, mapping,
fixtures и tests. Пользовательские workflow, которые уже покрываются
существующим catalog, не требуют участия разработчика Platform.
Технический runtime type publish boundary остаётся
`n8n-nodes-ndc.ndcDataProductPublish`; это идентификатор общего NDC package, а
не Gelios-specific node.
## Текущая готовность
Package, ontology links и synthetic fixtures проверены в source. Production
Engine L2 target использует зарегистрированный Gelios Bearer credential на
точном safe-read endpoint и managed `ndcDataProductWriterApi` generation g3;
оба binding записаны без раскрытия secret values. Canonical graph содержит
provider read, extraction, mapping и native `NDC Data Product Publish`.
Fresh production execution `881722` завершился успешно и опубликовал
`fleet.positions.current.v1`; managed writer g3 принят и имеет state `current`.
Это доказывает manual provider read → canonical Data Product path. Внутренняя
writer capability остаётся NODE.DC capability и не может быть заменена access
или refresh token Gelios.
Не доказаны: автоматический refresh, Schedule Trigger 10 секунд, несколько
последовательных scheduled cycles, retry/backoff, sampled history/retention на
continuous path и Foundry snapshot/patch → moving pins E2E. Provider package
остаётся declarative: MCP package discovery и package/profile → L2 graph
compiler пока отсутствуют, поэтому первый graph собран через общие Engine MCP
graph tools.

View File

@ -2,108 +2,202 @@
Package: `catalog/domain-packages/gelios` Package: `catalog/domain-packages/gelios`
Status: `v0.1.0` source-evidenced and product-required slice for the Robot2B client context and the Gelios provider. Status: `v1.0.0`, source-evidenced and product-required.
## Purpose and boundary ## Purpose and boundary
The package describes the meaning of Gelios fleet, telemetry, sensor, spatial, report and command concepts. It is not a Gelios client, token store, telemetry database, Cesium implementation or Engine workflow. The package describes Gelios fleet, telemetry, sensor, spatial, report and
command meanings. It is not a provider client, credential store, runtime
database, renderer implementation, NDC L1 agent or NDC L2 workflow.
The practical audit established a wide REST read surface: 107 currently visible units, 105 with `lastMsg`, a visible administrative account, unit groups, telemetry variants, sensor catalogues, command-template catalogues, geozone groups and report templates. The REST OpenAPI documents 295 operations. This ontology therefore separates three things that must not be conflated: Gelios is the first live provider used to prove the common provider-package
model. Runtime transport, collection cadence and semantic mapping belong to an
isolated NDC L2 connection instance. External Data Plane persists and delivers
provider-neutral Data Products. Ontology Core describes meaning only.
1. documented provider capability; The matching contract/data artifact is
2. actual runtime read evidence for the current account; `platform/packages/external-provider-contract/providers/gelios/v1`. Adding a
3. approved Robot2B product scope. second account creates another connection instance with different opaque
credential references; it does not create another ontology package, Platform
service or custom node.
The 95-unit legacy Engine snapshot is a temporary compatibility reference only. It is not the final business scope. The provider's current groups also do not resolve that scope automatically. A Robot2B owner must approve an allowlist or equivalent rule represented by `gelios.access_scope`. ## Provider authentication boundary
Gelios issues exactly two provider secret artifacts: an access token and a
refresh token. The current NDC L2 `httpBearerAuth` request binding uses the
access token. Automatic refresh has not been proven in the deployed runtime, so
the matching provider package records refresh as `operator_managed`; neither
token value belongs in Ontology, a workflow graph, MCP, Ops or a trace.
A credential label such as `read access` is operator metadata, not a Gelios
token scope. `gelios.units.current.read` is classified as read because the
approved workflow transport is `GET /api/v1/units`. The label does not create a
separate read token or constrain other rights that Gelios may have granted to
the same access token. The scoped Data Product writer credential later in the
runtime path is an internal NDC/External Data Plane capability, not a third
Gelios token.
## Dynamic connection scope
`gelios.access_scope` is derived from two inputs on every collection run:
1. safe-read capabilities selected in the connection profile;
2. entities currently visible to the bound provider credential.
For `gelios.units.current.read`, NDC L2 processes every valid unit returned by
`GET /api/v1/units`. Static unit IDs, owner allowlists and provider-group business
filters are forbidden at collection boundary. New units visible to the same
credential appear automatically. Foundry and other Data Product consumers own
layer selection, sorting, filtering and visibility.
Technical limits such as a 5000-fact batch ceiling, retry policy and response
validation protect runtime health; they are not business entity filters.
## Canonical value contracts ## Canonical value contracts
### Unit and device ### Unit and device
`gelios.unit` is the stable business subject. The durable key is a namespaced provider identity such as `gelios.unit:<sourceUnitId>`, never a renderer entity ID or a display name. A unit can use a `gelios.tracker_device`, belong to multiple `gelios.unit_group` records and expose sensor, fuel, maintenance and custom-field configurations. `gelios.unit` is a stable provider subject. The published v1 source ID is
`gelios-unit-<sourceUnitId>` inside immutable `(providerId, connectionId)`
scope; a renderer entity ID or display name is never identity. The provider ID
suffix is preserved as an exact string (including leading zeros) and is never
parsed or renumbered. A unit can use a
`gelios.tracker_device`, belong to multiple `gelios.unit_group` records and
expose sensor, fuel, maintenance and custom-field configurations.
Hardware IDs, IMEI, phones and decrypt-related fields are restricted or secret-class data. They are not part of the default Studio contract. Hardware IDs, IMEI, phones, address, decrypt-related fields, raw `params` and
unclassified sensor payloads are excluded by the v1 field policy. New provider
fields remain dropped until they are evidenced, classified and introduced by a
new package/ontology revision.
### Current telemetry ### Current telemetry and position
`gelios.telemetry_snapshot` is the normalized current state produced by the Gateway, with at least: `gelios.telemetry_snapshot` is normalized by the versioned NDC L2 mapping.
`gelios.position_fix` is its time-qualified spatial portion; it is not a pin or
other renderer object.
The first approved output is exactly:
```text ```text
unitSubjectId Data Product: fleet.positions.current.v1
observedAt version: 1.0.0
receivedAt ontology revision: ontology.map.moving_object.v1
position: { latitude, longitude, height?, course?, speed?, satellites? } semantic type: map.moving_object
operationalStatus delivery: snapshot+patch
sensorReadings[] history: sampled, latest entity per 60-second bucket, 90 days
counterValues[]
qualityFlags[]
source: gelios-rest
``` ```
The source supports a raw and a pure last-message variant. Pure data may include sensors, counters and accumulated fields. Raw messages and dynamic `params` remain behind restricted policy and may be retained separately for diagnostics only. `gelios.position_fix` is the time-qualified position portion used by spatial consumers; it is not itself a pin. Allowed Data Product fields are:
```text
course_degrees
display_name
elevation_meters
geometry
hdop
horizontal_accuracy_meters
object_kind
operational_status
position_source
position_valid
quality_flags
satellite_count
speed_kph
```
All attribute names are snake_case. GeoJSON `geometry` is omitted when valid
coordinates are unavailable, but the unit remains in the product with
`position_valid=false`, `operational_status=no_position` and appropriate
`quality_flags`. If the provider has no last-message timestamp, the collection
receive time becomes the explicit `observedAt` fallback so the credential-visible
unit is not silently dropped.
### Sensors and operational semantics ### Sensors and operational semantics
`gelios.sensor_definition`, `gelios.sensor_conversion` and `gelios.sensor_reading` separate stable device configuration, calibration and time-series readings. Fuel profile, maintenance plan and custom field are independent configuration concepts. No assumed sensor meaning should be added to analytics until its message parameter, measure and conversion semantics are approved. `gelios.sensor_definition`, `gelios.sensor_conversion` and
`gelios.sensor_reading` separate device configuration, calibration and
time-series values. Fuel profile, maintenance plan and custom field are
independent concepts. A sensor meaning cannot enter a Data Product until its
message parameter, unit, conversion, classification and field policy are
approved.
### Spatial binding and Cesium pins ### Spatial binding
Gelios is a source domain; Cesium is a renderer adapter. The binding is deliberately provider-neutral: Gelios is a source domain. The provider-neutral relation is:
```text ```text
gelios.unit gelios.unit + gelios.position_fix
-> map.moving_object (stable spatial subject) -> map.moving_object
gelios.position_fix -> fleet.positions.current.v1
-> map.moving_object (current position update) -> Foundry Data Product binding
map.moving_object -> map layers, selection and telemetry panel
-> map.pin + map.label + map.visibility_rule
-> map.renderer_adapter (Cesium today, replaceable later)
``` ```
The future map payload selects a stable `subjectId`, position, normalized operational status, observed time and approved label fields. It must not contain Gelios credentials, raw payloads or Cesium transient entity references. This is the replacement boundary for the current M-map node-to-Cesium coupling. The publish boundary contains stable facts only. It cannot contain provider
credentials, tenant/connection claims supplied by the workflow, raw payloads,
The Map Page resolves the selected Studio profile to the shared map.pin contract. The initial accepted presentation is an elevated spike — ground anchor, stem, outlined head and label — but that visual configuration remains on the Studio side. Gelios publishes data for a stable subject; it never publishes Cesium primitive settings, pin pixels or a renderer entity ID. pixel values, styles or transient renderer identities. The synthetic contract
fixture is
The proposed renderer-neutral payload is in [`examples/gelios-map-moving-object.fixture.json`](../examples/gelios-map-moving-object.fixture.json). It uses synthetic coordinates only: it specifies the interface between Gateway and Map View, not a live trike position and not a Cesium entity. [`examples/gelios-map-moving-object.fixture.json`](../examples/gelios-map-moving-object.fixture.json).
### Commands ### Commands
`gelios.command_template` and `gelios.command_group` are read-only catalogue concepts. `gelios.command_dispatch`, `gelios.command_delivery` and `gelios.command_audit` are a separate red domain. `gelios.command_template` and `gelios.command_group` are read-only catalogue
concepts. `gelios.command_dispatch`, `gelios.command_delivery` and
`gelios.command_audit` are a separate red domain.
No collection run, workflow, map click or autonomous agent may create a dispatch. A future dispatch requires an explicit human action, approved scope, selected unit/group and template/parameters, confirmation, an audit record and delivery-state reconciliation. This ontology package does not grant or test a write permission. No collection run, workflow, map click or autonomous agent may create a
dispatch. A future command path requires explicit human intent, confirmation,
separate authorization, immutable audit and delivery reconciliation. This
ontology and the Gelios v1 provider package expose no command transport.
## Future data plane ## Canonical runtime flow
The intended flow is:
```text ```text
Gelios REST OAuth Gelios safe-read API
-> server-side Gelios Gateway -> opaque Gelios access credential in NDC L2 Credentials
-> scope filter + field policy + normalizer -> isolated NDC L2 connection workflow
-> durable telemetry/configuration storage -> versioned semantic mapping
-> NDC level-2 workflow / internal data contract -> NDC Data Product Publish through a native scoped writer credential
-> Map View binding / Cesium renderer adapter -> External Data Plane
-> scoped snapshot + patch stream
-> Foundry Data Product binding
``` ```
`gelios.collection_run` and `gelios.ingestion_cursor` define collection governance independently of the transport implementation. They support bounded incremental history ingestion later: one approved unit scope, limited time window, field whitelist, volume limit and durable checkpoint. They do not imply that all history should be collected now. There is no Gelios-specific Platform service or database in this model.
Provider transport never enters External Data Plane or Foundry. `gelios.collection_run`
Database technology is intentionally not chosen by Ontology Core. The proposed platform data-plane boundary and its validation gates are documented in [`platform/docs/ADR_GELIOS_DATA_PLANE.md`](../../../docs/ADR_GELIOS_DATA_PLANE.md). The ontology supplies the record boundaries needed by that ADR. and `gelios.ingestion_cursor` describe audit and resumability independently of
physical storage.
## Guardrails ## Guardrails
- Ontology Core never stores secrets, runtime snapshots, full raw telemetry or large geometry datasets. - Ontology Core never stores credentials, account state, runtime snapshots,
- Current provider-account visibility is not the approved Robot2B scope; collection must enforce an owner-approved scope before every run. full raw telemetry or bulk geometry.
- GET alone does not make a route safe: login-as, temporary tokens, configuration and WLN download are excluded from telemetry collection. - Gelios authentication has exactly access and refresh artifacts; credential
- Geozones require paging, filtering, volume controls and map LOD. labels and workflow classifications must not be reinterpreted as additional
- Command send and mutation routes remain red even if the account has documented access. provider token types or scopes.
- Map selection and pins target stable domain subjects, not provider payload IDs or renderer objects. - Every safe-read collection includes all valid entities returned for the
bound credential; unit allowlists and group filters are not collection
scope.
- A read method alone does not make a capability safe: impersonation,
temporary credential issuance, configuration mutation and downloads remain
excluded until separately classified.
- Dynamic provider fields are dropped until evidenced and classified.
- Geozones and history require bounded loading, paging/cursors, retention and
volume controls.
- Command send and mutation capabilities remain red even when provider access
exists.
- Foundry targets stable domain subjects and Data Product fields, not provider
payloads or renderer identities.
## Evidence and next implementation decisions ## Evidence and evolution
Evidence is limited to official REST OpenAPI, safe GET results, historical Engine donor inspection and the MAP package. The next decisions outside this ontology package are: Evidence is limited to official Gelios API documentation, safe-read results,
historical NDC L2 donor inspection and provider-neutral map contracts. The
Robot2B pilot counts (107 credential-visible units and a 95-unit legacy
snapshot) are retained only as historical evidence of response shape and
cardinality. They do not define canonical IDs, collection allowlists or product
scope.
1. owner-approved Robot2B allowlist/rule; Extension order is mandatory: establish safe provider evidence, extend
2. field policy for Studio, analytics and raw retention; capability/field classification, update ontology and mapping revision, add
3. data-plane ADR for realtime telemetry and history; synthetic fixtures and contract tests, then deploy an updated NDC L2 workflow.
4. Gelios Gateway implementation boundary and deployment location;
5. fixture and renderer-adapter contract for Map View pins and labels.