NODEDC_PLATFORM/packages/external-provider-contract
Codex eab47bc1fa feat(provider-contract): add classified unit contact aspect 2026-07-24 09:31:34 +03:00
..
examples feat(connectors): version provider packages and ontology contracts 2026-07-17 18:08:36 +03:00
providers/gelios feat(provider-contract): add classified unit contact aspect 2026-07-24 09:31:34 +03:00
src feat(provider-contract): add classified unit contact aspect 2026-07-24 09:31:34 +03:00
test feat(provider-contract): add classified unit contact aspect 2026-07-24 09:31:34 +03:00
README.md feat(provider-contract): add Gelios v9 telemetry projection 2026-07-23 23:27:31 +03:00
package.json feat(provider-contract): compile governed L2 execution plans 2026-07-23 19:27:01 +03:00

README.md

External Provider Contract

Этот package — единственное общее место для формы внешних интеграций NODE.DC. Он не содержит provider secrets, customer records, runtime payloads или исполняемый connector code. src/index.mjs даёт dependency-free проверку минимальных v1 contracts; она запускается через npm run check.

Каждый NDC L2 connector instance должен поставлять совместимые versioned артефакты:

provider-manifest
connection-profile
capability-catalog
field-catalog
collection-profile
retention-profile
semantic-mapping-contract
read-model-contract
command-catalog (metadata only until a red command gateway is approved)

provider-manifest — декларативный template-артефакт: provider ID, ontology revision, NDC L2 template version, capability catalog и data-product IDs. Он не является tenant connection, не содержит endpoint/URL, credential reference, secret, ручной scope или executable provider code. Concrete non-secret connection profile принадлежит и версионируется вместе с конкретным NDC L2 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.

compileL2ExecutionPlan превращает package + connection instance в детерминированный nodedc.l2-execution-plan/v1: разрешает generic шаги collection/request/extract/mapping/publish, прикладывает полные декларативные request/mapping contracts и фиксирует SHA-256 digests package, profile, template, mapping, field policy, Data Product и telemetry registry. Plan по умолчанию компилируется версией 1.2.0; immutable plans 1.1.0 остаются валидируемыми для воспроизводимости уже материализованных legacy runtime. Версия компилятора выбирает только provider-neutral runtime semantics и не добавляет ветвление по provider ID. Plan содержит exact nodedc.l2-graph-blueprint/v1: линейный набор generic runtime node kinds, exact edges, config digests, credential slots, trigger/retry/batch/ cardinality policy и закрытую матрицу требуемых mapping operators. Blueprint не содержит credential reference и фиксируется отдельным SHA-256 digest. Runtime обязан fail-closed отклонить неизвестный operator, rule или geometry strategy; молча подставлять provider-specific code запрещено. В компиляторе нет веток по provider ID. Provider-specific URL, source paths и параметры являются данными immutable package. После материализации Engine attestL2ExecutionPlanMaterialization связывает exact plan/blueprint digest с exact graph revision/digest и точным списком materialized steps.

Динамическая телеметрия проходит отдельный nodedc.telemetry-field-registry/v1. Реестр хранит только имена source parameters, типы, статус классификации, sensitivity и разрешённые surfaces — никогда значения. Только approved + operational entries могут войти в Data Product/analytics/Foundry projection. observed, PII/identifier, command и restricted entries остаются audit-only; wildcard source key запрещён. Поэтому появление нового параметра у provider не меняет ядро и не открывает его автоматически потребителям.

Текущий production-shaped package — providers/gelios/v5. Он сохраняет два официальных Gelios REST safe-read: GET /api/v1/users/me/monitoring-config и GET /api/v1/units?incltrip=true, а также точный immutable output fleet.positions.current.v4@4.0.0 с revision ontology.map.moving_object.v3. Единственные monitoring-state fields — signal_state (active|inactive) и movement_state (moving|stopped), полученные из Ontology Gelios v1.1.0. Все Data Product fields используют snake_case. fieldContracts фиксирует тип и обязательность каждого поля, а закрытые state values проверяются и в provider mapping, и на каждом publish в External Data Plane. Native rotating credential использует access, а refresh остаётся внутри NDC L2 Credentials. Предыдущие Data Product versions не переписываются и остаются legacy-compatible.

Проверяемые v1 contracts

  • Connection — provider instance, tenant scope и ссылка на provider credential в NDC L2 Credentials. Любые token/secret/password-like поля запрещены; publisher представлен system-managed declaration/status без ref.
  • Collection Profile — явная policy сбора. manual не может скрыто содержать polling interval; realtime требует interval не чаще одного раза в секунду.
  • Data Product — нормализованный versioned output с semantic types, полями, optional fieldContracts и внутренней аудиторией. Если fieldContracts задан, он обязан покрывать точный набор fields; каждый contract задаёт type, required, optional closed enum и числовые bounds. Отсутствие contracts допустимо только для опубликованных legacy versions.
  • Intake Batch — canonical scoped record, который External Data Plane валидирует и сохраняет: source, contract revision, idempotency, restricted raw envelope и canonical facts. Его source содержит providerId, tenantId и connectionId. Writer-bound NDC L2 request намеренно не является готовым Intake Batch: Data Plane сначала materializes scope и лишь затем применяет этот contract. В canonical record нет provider field mapping, entity allowlist, token или renderer data. Inline raw.payload в v1 запрещён: если нужна provenance-ссылка, connector передаёт restricted raw.ref вместе с hash. Отдельный raw-vault может быть добавлен только отдельным ADR и не становится частью NDC L2 → Data Plane wire boundary.
  • NDC Foundry Binding — адресует data product только в конкретную цепочку Foundry Application → Page → approved slot; templateId можно сохранить как дополнительную типизацию, но он не заменяет applicationId и pageId. В binding запрещены provider transport, endpoint и credential reference.
  • Provider Manifest — статическое описание NDC L2 connector template, capability catalog и ontology/data-product contracts. Оно не может содержать tenant, connection, credential, secret или provider transport.

Data Product delivery contracts

Provider-neutral runtime использует отдельные wire schemas:

  • nodedc.data-product.publish/v1 — unscoped publish request от NDC Data Product Publish; содержит только batch identity и canonical facts;
  • nodedc.data-product.snapshot/v1 — согласованный current snapshot с cursor;
  • nodedc.data-product.history/v1 — bounded Timescale history window с from/to, provider-neutral sourceIds, resolution и opaque keyset cursor;
  • nodedc.data-product.patch/v1 — committed upsert operations из durable outbox с previousCursor/cursor.

Publish request не может задавать provider, tenant, connection, endpoint, receivedAt, ontology revision, version или persistence policy. Data Plane materializes эти значения из opaque writer binding и зарегистрированного Data Product definition. Snapshot/stream читаются только по отдельному opaque reader binding; shared internal bearer и caller-provided scope headers являются legacy и не используются новыми nodes/Foundry runtime.

snapshot+patch v1 — bounded contract: один scoped Data Product содержит не более 5000 current entity keys (sourceId, semanticType). Snapshot обязан быть полным и привязанным к одному repeatable-read cursor. Параметр limit задаёт защитный ceiling, не page size: превышение возвращает 413 data_product_snapshot_limit_exceeded, а reader не имеет права начинать stream с усечённой базой. nextPageCursor зарезервирован для будущего отдельно версионируемого query contract и текущим bounded runtime не выдаётся.

Для большей current cardinality definition заранее раскладывается по стабильным partition Data Products с независимыми snapshot/patch cursors. History является отдельным immutable-window query: cursor привязан digest-ом к exact from/to/resolution/sourceIds, поэтому его нельзя переиспользовать с другим запросом; offset pagination запрещена. Offset/source-ID pagination поверх меняющегося current snapshot запрещена: между страницами она способна потерять или задублировать изменения относительно patch cursor.

Для каждого canonical fact действует одинаковый hard ceiling: сериализованный attributes не больше 64 KiB как на publish/intake входе, так и в snapshot/patch выходе. GeoJSON Point принимает longitude только в [-180, 180], latitude в [-90, 90]; batch sequence ограничен диапазоном PostgreSQL integer 0..2147483647. Эти ограничения нельзя ослабить опциями конкретного caller-а.

Manifest, connection/collection profiles, data-product definition и Foundry binding используют fail-closed allowed-key schemas. Неизвестные поля, а также secret-like имена или значения (включая ndc_edpwb_, ndc_edprb_ и ndc_edppr_) отклоняются на общей границе.

Все private custom nodes NODE.DC поставляются package platform/packages/n8n-nodes-ndc. Их display name обязан начинаться с NDC , а runtime type — с n8n-nodes-ndc.; package называется строго n8n-nodes-ndc. Provider-specific adapters остаются NDC L2 workflow logic и не становятся custom nodes или ветками Data Plane. Эти инварианты проверяются package test.

Control-plane команда NDC Foundry Binding имеет отдельную replay-safe schema nodedc.foundry.binding-upsert/v1 (validateFoundryBindingUpsert). Это не declarative provider artifact и не runtime transport: команда содержит только application/page/binding/data-product projection и idempotency key, а право на операцию извлекается Foundry из отдельного opaque workload grant.

Native NDC L2 Credentials

Provider auth и внутренние workload capabilities используют один общий секретный boundary — native Credentials NDC L2, — но являются разными credential domains. После сохранения ядро владеет secret, а graph и MCP используют только opaque reference.

Gelios выдаёт ровно access token и refresh token. Production credential type ndcProviderRotatingAccessApi использует access token для HTTP request и выполняет refresh → access в native n8n preAuthentication; provider package v3 фиксирует refreshMode: runtime_managed. Оба secret artifact остаются в native Credentials и не попадают в graph, package или trace. Название credential с текстом вроде read access является лишь локальной меткой; read-классификацию задают разрешённые 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.

Для общего Data Product transport используются ndcDataProductWriterApi, ndcDataProductReaderApi и ndcFoundryBindingApi. Это внутренние NDC capabilities, не Gelios tokens и не часть Gelios token lifecycle. Их значения не являются частью provider package и не передаются между workflow nodes как данные.

Connection caller передаёт только provider credential reference. Publisher role остаётся credential binding для NDC Data Product Publish, но template маркирует его management: control_plane_managed: ни writer secret, ни writer reference не входят в connection parameters. Instantiation возвращает декларативный systemBindings.publisher с desired state и unresolved status; дальше его разрешает trusted control plane.

Canonical managed EDP writer capability генерируется и сохраняется внутри Engine credential boundary, а EDP получает только digest. Старые manual writer/reader и Foundry issuance paths могут возвращать capability один раз trusted control-plane caller и остаются отдельно закрытыми compatibility границами; MCP и пользователь получают только opaque reference/status. Запрещены password input как пользовательский acceptance-путь, graph parameters, connection/profile files, env, Ops, logs и traces.

Broad credential sink/resolver/daemon и provider-specific provisioning запрещены. Требуется узкий generic ensure data-product publish grant contract с server-derived scope, idempotency, CAS/crash recovery и audit. Текущий deployed Engine MCP этой операции ещё не имеет; root/UI transfer допустим только как emergency self-hosted diagnostics, не как user journey.

NDC L2 private-extension management

src/engine-private-extension.mjs задаёт строгую Platform-side границу для NDC L2-owned активации проверенного n8n-nodes-ndc release. Контракт не устанавливает package и не меняет NDC L2: он фиксирует async plan -> apply receipt -> operation/status protocol, где apply обязан быстро вернуть queued и operationId, а долгий recreate/acceptance отслеживается отдельно.

Активация и rollback требуют отдельной глобальной capability engine.private-extension.manage. Обычные NDC L1/NDC L2 grants её не дают. Чтение состояния допускает engine.private-extension.read или manage-capability. Запрос выбирает только allowlisted package, digest-bound releaseId и packageSha256; caller не передаёт host path, package bytes, Compose service, shell command или credential material. Plan живёт не более 15 минут, является single-use и применяется только с тем же idempotency key и plan hash.

Runtime transition использует platform-managed community-package loader для immutable n8n-nodes-ndc release; private runtime paths и environment settings не являются частью публичного provider contract:

  • Deploy/Run quiesced and execution queue drained before the version switch;
  • read-only mount and atomic current/recovery state;
  • force-recreate main, every worker and every webhook instance as one version barrier; hot reload запрещён;
  • acceptance требует единый generation и точный набор трёх package-qualified node schemas и трёх credential schemas.

Любая ошибка после switch запускает automatic rollback и повторный force-recreate/acceptance. Ошибка самого rollback переводит runtime в quarantined. Immutable release и существующие NDC L2 Credentials сохраняются. Для первой активации предыдущим проверенным состоянием является n8n-nodes-ndc.inactive/v1: rollback в этот baseline удаляет package из loader surface, но не удаляет credentials.

Public Ops gateway уже умеет прозрачно передавать эти операции через /engine/mcp, если NDC L2 реализует соответствующие MCP tools. Так как gateway имеет 30-second upstream timeout, side effect остаётся асинхронным; отдельный public REST proxy для management boundary не требуется.

providers/gelios/v1/fixtures содержит synthetic provider response и ожидаемый publish contract без customer, tenant identity или credential material.

Ownership

  • platform/packages/external-provider-contract — общий контракт и schemas.
  • NDC L2 — provider API adapter: fetch, pagination, batching, semantic mapping, collection profile и ссылка на credential в NDC L2 Credentials.
  • Platform External Data Plane — provider-neutral intake, raw retention, canonical facts, current/history projections и scoped read products. Он не знает provider fields, customer/business filters или renderer rules.
  • platform/services/ontology-core/catalog/domain-packages/<provider> — семантика, отношения и guardrails, но не runtime data.
  • Foundry/interface bindings — consumers scoped data product; они не получают provider transport, endpoint или credential reference.

services/<provider>-gateway — устаревший experimental path и не является шаблоном новых integration services. Канон описан в docs/ADR_L2_OWNED_EXTERNAL_CONNECTORS.md.

Mandatory connection boundary

connection принадлежит одному tenant/client context и содержит только ссылку на provider secret, утверждённый capability scope, collection profile, field policy и retention policy. Во всех runtime records Data Plane сохраняет минимум tenant_id, connection_id, provider_id, observed_at, received_at и provenance/version там, где это применимо.

Provider credential reference текущего Gelios request адресует access token; наличие 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

Writer binding — EDP-owned runtime security state, а не versioned artifact provider manifest или connection profile. Он фиксирует tenantId, connectionId, providerId, allowedDataProductIds, active/revoked state и expiresAt. NDC L2 не может редактировать binding или задавать его scope; изменение любого scope-поля либо TTL создаёт новый binding, а прежний binding можно только rotate/revoke.

Binding создаёт, сохраняет и привязывает только trusted control plane. Caller connection instance не принимает writerCredentialRef; publisher role в L2 template является обязательным control_plane_managed binding requirement.

Новый NDC L2 вызывает POST /internal/data-plane/v1/data-products/:dataProductId/publish с opaque writer capability и envelope nodedc.data-product.publish/v1. В body есть только batch identity и canonical facts; source, provider, tenant, connection, ontology revision, product version и persistence policy запрещены. EDP проверяет binding и path dataProductId, materializes immutable scope и contract из server-owned registries, затем сохраняет canonical batch. Caller-provided scope отклоняется, а не доверяется и не объединяется с binding.

Plaintext writer capability запрещена в provider package, connection/profile, NDC L2 graph, files, environment, logs/traces, raw payload, Foundry и MCP. В canonical пути она генерируется и сохраняется внутри native Engine credential boundary; EDP получает только SHA-256 digest, а workflow — только opaque reference/status. Старые ручные EDP create/rotate endpoints с one-time plaintext response остаются отдельно выключенным compatibility-механизмом и не являются пользовательским или acceptance-путём.

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 reader хранит два разных provider-neutral scope. connectionId в reader grant идентифицирует consumer connection, а sourceConnectionId фиксирует writer connection, из которого разрешено читать Data Product. sourceConnectionId не приходит из L2 graph: при ensure EDP разрешает его по активным writer bindings того же tenantId, providerId и полного набора allowedDataProductIds. Точное совпадение connection выбирается напрямую; иначе допускается только один кандидат. Ноль кандидатов возвращает managed_reader_source_scope_not_found, несколько — managed_reader_source_scope_ambiguous. Поэтому reader из отдельного L2 target не попадает в пустой consumer scope, а несколько provider accounts никогда не смешиваются эвристически. Legacy reader binding сохраняет прежнюю семантику: его connectionId одновременно является source connection.

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

POST /internal/data-plane/v1/intake — временный compatibility route для existing writers. Он принимает только canonical scoped Intake Batch под NODEDC_INTERNAL_ACCESS_TOKEN и требует совпадения body scope с x-nodedc-tenant-id и x-nodedc-connection-id. Writer token этот route не заменяет.

Для миграции: Platform обеспечивает scoped writer binding и native credential, затем через Engine MCP новый workflow branch переключается на Data Product publish route, удаляет tenant/connection из body и headers и проверяет успешный publish. Существующий legacy workflow/credential остаётся неизменным, пока владелец отдельно не решит его вывести; новый writer не должен fallback-иться на legacy route после ошибки publish.

Intake boundary

NDC L2 отправляет в общий Data Plane publish request, а не SQL-запрос в общие таблицы. Platform проверяет boundary и сохраняет raw/history/current projections; semantic mapping остаётся в NDC L2. Список конкретных source IDs не является частью connection scope: scope выбирает read-capability API, а visibility решает data-product consumer.

Inline raw.payload запрещён: NDC L2 не может записать в Data Plane полный ответ provider-а или произвольную строку. Пока отдельный raw-vault не утверждён, batch либо не содержит raw, либо содержит только restricted raw.ref и hash. Secret-like keys и распознаваемые bearer/JWT/writer-token values в любом участке canonical batch, включая facts[].attributes, отклоняются. Retention raw reference вычисляется от server acceptance time, а не от batch.receivedAt; service делает sweep expired envelopes при старте и по расписанию.

Safety classification

Capabilities классифицируются как read, metadata, write, destructive или unknown. Только read и согласованные metadata могут попасть в collector. write и destructive остаются каталогизированными, но не имеют transport route в read adapter. Эта классификация относится к разрешённому method/endpoint workflow, а не утверждает, что provider access token имеет read-only scope.