README.md

registry

Целевая модель экосистемы и границы текущей реализации: архитектура.

registry — открытый git-first реестр универсальных действий бизнеса, программ и оборудования, их контрактов, провайдеров, возможностей и адаптеров. Он задаёт общий язык взаимодействия для ERP, САПР, сервисов и агентов, независимо от VARM или конкретного поставщика. Операция может иметь прямые и составные реализации через другие операции, которые также допускают дальнейшее разложение. Registry не является обязательным сервером исполнения.

Операции и реализации: композиция, гарантии и порядок внедрения. CAD: базовые draft-операции.

Проект живет в экосистеме opsregistry:

  • opsregistry/registry — основной репозиторий-источник истины;
  • @opsregistry/contracts — опубликованные Zod-контракты и типы операций;
  • @opsregistry/adapter-* — независимые runtime-адаптеры providers;
  • opsregistry/bridge и @opsregistry/bridge-client — локальные операции ОС и браузерный клиент;
  • opsregistry/site — публичный каталог https://opsregistry.org для людей и агентов.

Текущее состояние экосистемы и точка продолжения описаны в docs/agent-handoff.md.

Что здесь хранится

  • Домены: верхнеуровневые предметные области, например logistics, inventory или finance.
  • Операции: самостоятельные осмысленные действия, например logistics.shipment.quote, logistics.shipment.create или cad.solid.extrude.
  • Контракты: схемы входа/выхода, ошибки и семантика, реализуемые в @opsregistry/contracts.
  • Провайдеры: конкретные организации, сервисы или системы, например cdek и energy.
  • Возможности: публичные декларации о том, что конкретный провайдер выполняет конкретную операцию.
  • Адаптеры: реализации общего контракта для конкретной системы — напрямую через API/SDK либо через вызовы других операций.

Чем отличаются операция, провайдер, возможность и адаптер

Операция отвечает на вопрос «что мы хотим сделать?». Это общий контракт без привязки к конкретной компании.

Провайдер отвечает на вопрос «кто умеет это делать?». Это описание конкретного контрагента или внешней системы.

Возможность отвечает на вопрос «какой провайдер публично заявляет, что умеет выполнять эту операцию?». Именно она связывает provider и operation на уровне реестра.

Адаптер отвечает на вопрос «как именно эта возможность подключается к реальной внешней системе?». Он нужен не всегда. Если исполнение уже живет в общем runtime приложения-потребителя, адаптер может не понадобиться вовсе.

Один провайдер может публиковать много возможностей. Для части таких возможностей могут существовать отдельные адаптеры.

Операция определяется контрактом результата, а не глубиной разложения. Её составная реализация вызывает другие операции, сохраняя общий контракт и его гарантии. Разложение принадлежит реализации; единственного обязательного дерева подопераций нет. Сначала это может быть обычный код адаптера. Общий декларативный формат пока не реализован. Сценарий — пользовательский шаблон на том же механизме, а не второй тип исполнимой операции. Не каждый шаблон становится канонической операцией. Экземпляры выполнения (runs), права и состояние принадлежат runtime-потребителю; общая транзакция не предполагается.

Домены finance, accounting и workforce намеренно разделены. finance отвечает за движение денег, accounting — за отражение хозяйственных событий в учёте, а workforce — за отношения с сотрудниками и подрядчиками. Например, расчёт начислений, бухгалтерская проводка и фактическая выплата являются тремя разными операциями и могут исполняться разными системами.

Физические запасы относятся к inventory: этот домен отвечает за доступность, резервирование, приёмку, выдачу и перемещения. logistics отвечает за перевозку между адресами и взаимодействие с перевозчиками, а storage — за файлы и объектное хранение. Сквозной процесс может использовать все три домена, не смешивая их состояние и операции.

Электронный обмен деловыми документами относится к documents: получение, отправка, вложения, подписи и события оператора ЭДО не являются бухгалтерскими проводками. После получения документа приложение может отдельно выполнить операции accounting, поэтому один модуль продукта вправе использовать оба домена, не объединяя их контракты.

Всемирный реестр и юрисдикции

OpsRegistry задуман как всемирный реестр, но универсальность не означает одинаковые правила во всех странах. Реестр разделяет три уровня:

  1. Операция фиксирует устойчивое бизнес-намерение, результат и жизненный цикл, например accounting.journalEntry.post или accounting.period.close.
  2. Профиль задаёт юрисдикцию, стандарт учёта, налоговый режим, план счетов и обязательные поля, например страну, штат или IFRS, US GAAP, RAS.
  3. Capability и адаптер сообщают, какой профиль и вариант контракта реально поддерживает конкретный провайдер.

Страну, регион и стандарт не добавляют в operationCode, если они только уточняют правила той же операции. Новый код создают, когда меняются юридический результат, стороны, жизненный цикл, статусы или обязательный побочный эффект. Так общий язык остаётся мировым, а различия явно доступны человеку и агенту вместо скрытых предположений внутри адаптера.

Режим авторизации относится не к операции вообще, а к конкретной capability и ее адаптеру. Одна и та же операция у разных провайдеров может быть:

  • только публичной;
  • только авторизованной;
  • доступной в обоих режимах одновременно.

Именование операций, провайдеров и адаптеров

Операции

Код операции описывает универсальный смысл действия, а не API конкретного поставщика.

domain.resource.action

Например:

  • logistics.shipment.quote
  • finance.merchantPayment.create
  • manufacturing.workpiece.cut
  • it.identity.token.revoke
  • documents.exchangeDocument.listChanges
  • documents.exchangeDocument.create
  • documents.exchangeDocument.send
  • documents.exchangeDocument.accept
  • documents.exchangeDocument.reject
  • documents.exchangeDocument.requestCancellation
  • documents.exchangeDocument.acceptCancellation
  • documents.exchangeDocument.rejectCancellation
  • documents.exchangeDocument.prepareSigning
  • documents.exchangeDocument.completeSigning
  • trust.certificate.list
  • trust.certificate.resolve
  • trust.device.list
  • trust.signingEnvironment.inspect
  • trust.signingEnvironment.configure
  • trust.digitalSignature.create

domain отвечает за крупную область, например finance, logistics, manufacturing. resource отвечает за предмет действия, например shipment, merchantPayment, workpiece, sketch. action — это глагол, описывающий наблюдаемое действие: create, quote, track, cut. Для вложенного ресурса допустимы дополнительные сегменты, например it.identity.token.refresh.

Не добавляйте в operationCode провайдера, бренд, API-метод или технологию, если это не меняет бизнес-смысл операции. Правильно: finance.merchantPayment.create. Неправильно: finance.tbank.init или finance.sbpQr.tbankCreate.

Двухчастные названия вроде manufacturing.cutting или rental.vehicle являются категориями, а не исполнимыми операциями. Метод, материал и технология передаются параметром контракта или тегом capability. Полные правила, словарь глаголов и критерии добавления новой операции описаны в docs/operation-naming.md.

Человекочитаемые поля операции поддерживают локализацию. Английский текст в title, summary, description, requestDescription и responseDescription является каноническим, а переводы хранятся в translations по BCP 47 language tag, например translations.ru. Код операции, версия, статус и теги не переводятся. Клиенты должны использовать выбранный перевод с откатом на канонический английский текст.

Новая операция нужна, когда меняются стороны процесса, жизненный цикл, статусы, юридический смысл, требования к фискализации или модель авторизации. Если меняется только способ исполнения внутри того же процесса, это обычно параметр операции или отдельная capability.

Например, finance.fiscalReceipt.create и finance.fiscalReceipt.createClosing намеренно разделены. Первый код означает обычную регистрацию фискального чека, а второй — закрывающий чек после ранее принятой предоплаты или аванса. Для автора адаптера это важная подсказка: TBank мапит обычный чек в Init.Receipt или Cancel.Receipt, а закрывающий чек — в отдельный метод /cashbox/SendClosingReceipt.

Провайдеры

providerCode должен быть стабильным идентификатором конкретной организации, сервиса или внешней системы. Он не обязан совпадать с красивым названием бренда.

Если название неоднозначное, добавляйте уточняющий домен:

  • energy-logistics
  • energy-metalcutting
  • tbank
  • cloudkassir

В title можно оставить бренд, а в summary, tags, countryCodes и taxIds уточнить, о какой организации идет речь.

Capabilities и adapters в реестре

Коды capability и adapter metadata строятся от провайдера и операции:

provider-domain-resource-action

Например:

  • tbank-finance-merchant-payment-create
  • tbank-finance-merchant-payment-create-qr
  • cloudkassir-finance-fiscal-receipt-create

Если providerCode уже содержит уточнение, оно остается в начале:

  • energy-logistics-logistics-shipment-quote
  • energy-metalcutting-manufacturing-workpiece-cut

Runtime-пакеты адаптеров

Runtime-пакет можно называть по провайдеру, если один пакет покрывает один понятный внешний API:

  • @opsregistry/adapter-cdek
  • @opsregistry/adapter-cloudkassir

Если у бренда много разных API-доменов, добавляйте домен интеграции:

  • @opsregistry/adapter-tbank-acquiring
  • @opsregistry/adapter-tbank-payouts
  • @opsregistry/adapter-energy-logistics

Провайдер в названии пакета не заменяет operationCode: пакет говорит, как подключиться к конкретному API, а операция говорит, какой универсальный бизнес-смысл выполняется.

Что такое адаптер

Адаптер в этом проекте адаптирует не к конкретному приложению и не к конкретной платформе. Он адаптирует внешний интерфейс поставщика к универсальной операции реестра.

Например, если транспортная компания имеет собственное API расчета доставки, адаптер приводит это API к операции logistics.shipment.quote. После этого любая система может использовать общий контракт операции, не зная внутренних деталей API конкретного поставщика.

Из этого следует важное правило: адаптеры должны быть переиспользуемыми. Ими может пользоваться любое приложение, которое поддерживает модель opsregistry, а не только один конкретный продукт.

Реестр и runtime

Текущий репозиторий хранит декларации и публичное API:

  • какие операции существуют;
  • какие провайдеры существуют;
  • какие capabilities опубликованы;
  • какие адаптеры описаны на уровне метаданных.

Но реальный исполняемый код адаптеров лучше держать отдельно. У runtime-адаптеров обычно свой жизненный цикл:

  • отдельные версии;
  • собственные changelog и release notes;
  • свои зависимости от SDK и HTTP-клиентов;
  • отдельные тесты и примеры использования.

Поэтому хороший следующий шаг — хранить исполняемые адаптеры в отдельных пакетах, а в этом репозитории оставлять только реестр и описание того, что такие адаптеры существуют.

Реестр и статусы заказов

Реестр не хранит состояние конкретных заказов, оплат, производственных этапов или отправок. Он отвечает только за справочник: какие универсальные операции существуют и какие публичные провайдеры заявляют, что умеют их выполнять.

Статусы конкретного заказа должны жить в системе, где этот заказ исполняется. Например, карточка заказа может собирать три независимые линии статусов:

  • оплаты: счет выставлен, частично оплачен, оплачен, возврат, просрочка;
  • производство: раскрой, гибка, сборка, сварка, опрессовка, упаковка;
  • отправка: создана отгрузка, передана перевозчику, в пути, доставлена, проблема доставки.

Для производственного заказа удобно хранить отдельные operation executions: каждая запись ссылается на operationCode, заказ, внутреннего исполнителя, плановые/фактические даты и runtime-статус вроде planned, inProgress, done, blocked или cancelled. providerCode из публичного реестра нужен только тогда, когда контрагент действительно опубликовал capability наружу.

Так реестр остается универсальным и публичным, а ERP, WMS, TMS или другая система может нативно показывать фактическую стадию заказа без привязки к внутренним деталям реестра.

Как лучше пакетировать адаптеры

Есть два основных варианта.

Пакеты по поставщику

Пример:

  • @opsregistry/adapter-cdek
  • @opsregistry/adapter-energy

Внутри одного пакета живут все операции конкретного поставщика: logistics.shipment.quote, logistics.shipment.create, logistics.pickup.create и так далее.

Плюсы:

  • в реальной жизни интеграция обычно строится вокруг одного внешнего API;
  • удобно переиспользовать общую авторизацию, конфиг, типы ошибок и transport layer;
  • проще версионировать изменения поставщика в одном месте;
  • проще тестировать все сценарии конкретного провайдера как единое целое.

Минусы:

  • пакет со временем может разрастаться;
  • если нужна одна операция, подключается пакет с более широким набором возможностей.

Пакеты по операции

Пример:

  • @opsregistry/logistics-shipment-quote-adapters
  • @opsregistry/logistics-shipment-create-adapters

Внутри пакета живут адаптеры сразу для нескольких поставщиков по одной операции.

Плюсы:

  • удобно потребителю, который мыслит одной операцией;
  • легче собрать сравнительный runtime для одной задачи, например агрегатор тарифов.

Минусы:

  • повторяется код авторизации и низкоуровневой интеграции с одним и тем же поставщиком;
  • сложнее сопровождать, когда у поставщика много связанных операций;
  • версии и фиксы по одному провайдеру расползаются по нескольким пакетам.

Рекомендация для первого этапа

Для первого этапа разумнее ориентироваться на пакеты по поставщику.

Это лучше совпадает с реальной природой интеграций: внешний API, его авторизация, ограничения, ошибки и особенности обычно общие для всех операций одного поставщика. Если позже появится явная потребность в operation-centric композиции, поверх provider-пакетов можно сделать отдельные агрегирующие пакеты или готовые runtime-наборы.

Почему git-first

  • Источник истины лежит прямо в репозитории.
  • Все изменения проходят обычный code review.
  • История операций, провайдеров, возможностей и адаптеров прозрачно видна в git.
  • На первом этапе не нужна отдельная админка, база данных или сложная панель управления.
  • API остается тонким слоем чтения, валидации и OpenAPI-документации поверх содержимого репозитория.

Структура

data/
  domains/       # JSON-описания верхнеуровневых предметных областей
  operations/    # JSON-описания универсальных операций
  providers/     # JSON-описания провайдеров
  capabilities/  # JSON-описания опубликованных возможностей
  adapters/      # JSON-описания адаптеров для внешних интеграций

src/lib/registry/
  definitions.ts        # Zod-схемы и типы реестра

src/lib/server/registry/
  filesystem.ts         # Чтение данных реестра с диска

src/lib/server/api/endpoints/
  domains.ts            # Контракт GET /api/domains
  operations.ts         # Контракт GET /api/operations
  capabilities.ts       # Контракт GET /api/capabilities
  providers.ts          # Контракт GET /api/providers
  openapi.ts            # Контракт GET /api/docs/openapi.json

Папка data/ нужна затем, чтобы явно отделить данные реестра от кода приложения. Репозиторий уже называется registry, поэтому вложенный catalog/ или registry/ только создавал бы лишнюю путаницу.

API

  • GET /api/domains — список предметных областей операций.
  • GET /api/operations — список операций.
  • GET /api/capabilities — список опубликованных возможностей.
  • GET /api/providers — список провайдеров с их возможностями и адаптерами.
  • GET /api/docs/openapi.json — OpenAPI-спецификация API.

Все endpoint-ы описаны через общий API helper, поэтому в одном месте живут:

  • runtime-валидация через Zod;
  • типы ответов;
  • OpenAPI-метаданные;
  • SvelteKit-хендлеры.

Разработка

bun install
bun run dev

Проверка типов и SvelteKit-конфигурации:

bun run check

Как добавить новую операцию

  1. Проверьте смысл по docs/operation-naming.md. Убедитесь, что нужна новая операция, а не параметр или сценарий.
  2. Добавьте домен в data/domains, если его ещё нет.
  3. Создайте JSON-файл в data/operations.
  4. Укажите уникальный code в формате domain.resource.action.
  5. Заполните каноническое английское описание и переводы.
  6. Если операцию уже кто-то публикует, добавьте capability в data/capabilities.

Пример:

{
  "code": "inventory.stock.reserve",
  "title": "Reserve inventory",
  "summary": "Reserve available stock for a sales flow.",
  "status": "draft",
  "version": "0.1.0",
  "tags": ["inventory", "warehouse"],
  "translations": {
    "ru": {
      "title": "Резервирование запаса",
      "summary": "Зарезервировать доступный запас для продажи."
    }
  }
}

Как добавить провайдера

  1. Создайте JSON-файл в data/providers.
  2. Используйте короткий стабильный code, например pek или moisklad.
  3. Укажите понятное название, краткое описание и при необходимости ссылку на сайт.

Пример:

{
  "code": "pek",
  "title": "PEK",
  "summary": "Federal logistics provider.",
  "status": "draft",
  "countryCodes": ["RU"]
}

Как добавить capability

  1. Создайте JSON-файл в data/capabilities.
  2. Свяжите его с существующими providerCode и operationCode.
  3. Укажите executionMode: native, если выполнение живет в общем runtime потребителя. adapter, если нужен интеграционный мост к внешнему API.
  4. Заполните authModes и defaultAuthMode, чтобы зафиксировать режимы доступа к операции.
  5. Если выбран режим adapter, при необходимости добавьте техническое описание в data/adapters.

Пример:

{
  "code": "cdek-logistics-shipment-quote",
  "providerCode": "cdek",
  "operationCode": "logistics.shipment.quote",
  "title": "CDEK shipment quote capability",
  "summary": "CDEK publicly offers shipment quote calculation.",
  "status": "draft",
  "executionMode": "adapter",
  "visibility": "public",
  "authModes": ["public", "authorized"],
  "defaultAuthMode": "public"
}

Как добавить адаптер

  1. Создайте JSON-файл в data/adapters.
  2. Свяжите его с существующей capabilityCode, а также с providerCode и operationCode.
  3. Заполните authModes и defaultAuthMode с учетом реального поведения внешнего API.
  4. Опишите текущее состояние интеграции и базовый смысл адаптера.
  5. Если появится исполняемая реализация, держите ее в отдельном runtime-пакете и используйте JSON-файл реестра как декларацию существования и совместимости адаптера.

Пример:

{
  "code": "pek-logistics-shipment-quote",
  "capabilityCode": "pek-logistics-shipment-quote",
  "providerCode": "pek",
  "operationCode": "logistics.shipment.quote",
  "title": "PEK shipment quote adapter",
  "summary": "Maps the universal shipment quote contract to PEK APIs.",
  "status": "draft",
  "authModes": ["authorized"],
  "defaultAuthMode": "authorized",
  "implementationStatus": "stub"
}

Что пока не делаем

  • авторизацию;
  • базу данных;
  • админку;
  • сложный UI;
  • реальные HTTP-интеграции с внешними системами внутри этого репозитория;
  • публикацию runtime-адаптеров из этого репозитория.

Проверка TypeScript

svelte-check запускается с --tsgo: typescript@6 предоставляет необходимый Svelte JS API, а @typescript/native является алиасом TypeScript 7 и выполняет диагностику. После bun update --latest не заменяйте эту пару одним typescript@7; закрепите TS6 обратно и выполните bun run check.

Описание
Открытый git-first реестр универсальных бизнес-операций, провайдеров, возможностей и адаптеров
Конвейеры
0 успешных
0 с ошибкой
Разработчики