README.md

@opsregistry/contracts

Пакет со спецификациями операций для экосистемы opsregistry.

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

  • opsregistry/registry для описания операций;
  • runtime-адаптеры поставщиков;
  • приложения-потребители, которым нужна единая модель входа и выхода.

Что внутри

Сейчас пакет содержит первые спецификации из домена логистики:

  • logistics.shipment.quote
  • logistics.shipment.create
  • logistics.shipment.list
  • logistics.shipment.track
  • logistics.pickup.create

И первые спецификации из домена финансов:

  • finance.merchantPayment.create
  • finance.merchantPayment.createQr
  • finance.merchantPayment.getStatus
  • finance.merchantPayment.cancel
  • finance.fiscalReceipt.create
  • finance.fiscalReceipt.getStatus

И первые спецификации из домена S3-совместимого объектного хранилища:

  • storage.object.uploadFile
  • storage.object.deleteFile
  • storage.object.list
  • storage.website.put
  • storage.website.get
  • storage.website.delete

И спецификации физических запасов и склада:

  • inventory.stock.getAvailability
  • inventory.stock.receive
  • inventory.stock.issue
  • inventory.stock.reserve
  • inventory.stock.release
  • inventory.stock.transfer
  • inventory.stock.adjust

И первые спецификации электронного обмена документами:

  • documents.exchangeDocument.list
  • documents.exchangeDocument.get
  • 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.signingEnvironment.inspect
  • trust.signingEnvironment.configure
  • trust.device.list
  • trust.certificate.list
  • trust.certificate.resolve
  • trust.digitalSignature.create

Каждая операция экспортирует:

  • operationCode
  • Zod-схемы входа и выхода
  • TypeScript-типы
  • минимальные метаданные режима доступа, если операция может выполняться публично или с авторизацией

Как этим пользоваться

import {
  shipmentQuoteInputSchema,
  type ShipmentQuoteInput,
  type ShipmentQuoteResult,
} from "@opsregistry/contracts/logistics/shipment-quote";

Пример резервирования запаса:

import { stockReserveInputSchema } from "@opsregistry/contracts/inventory/stock-reserve";

const request = stockReserveInputSchema.parse({
  demandReference: { type: "salesOrder", id: "SO-42" },
  lines: [
    {
      item: { sku: "PART-001" },
      quantity: { value: 2.5, unitCode: "KGM" },
    },
  ],
  idempotencyKey: "SO-42:reserve:1",
});

Принцип

У каждой операции своя собственная спецификация.

Пакет @opsregistry/contracts не означает “один общий контракт для всех операций”. Он означает единое место, где живут согласованные спецификации конкретных операций, чтобы разные адаптеры и приложения использовали один и тот же смысл операции.

Например, logistics.shipment.quote уже умеет выразить:

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

logistics.shipment.track описывает чтение состояния уже созданного отправления:

  • вход принимает shipmentId, trackingNumber, providerDocumentId, customerReference или externalIds;
  • выход возвращает нормализованный статус отправления;
  • история движения передается как массив событий с кодом провайдера, временем, локацией и исходным payload.

Граница электронного документооборота

documents.exchangeDocument.create создаёт у оператора черновик пакета с участниками и вложениями. Операция не подписывает и не отправляет документ. clientReference связывает пакет с объектом приложения, а idempotencyKey позволяет провайдеру или адаптеру защититься от повторного создания, если они поддерживают идемпотентность.

documents.exchangeDocument.send является отдельным юридически значимым действием. Вход всегда требует confirmLegalAction: true и явный signingMode: без подписи, подписью выбранной провайдером либо отложенной подписью. Закрытые ключи и PIN не входят в контракт. Внешняя подпись выполняется отдельной операцией trust.digitalSignature.create после подготовки подписываемых объектов.

documents.exchangeDocument.accept подтверждает деловое содержание входящего документа, а documents.exchangeDocument.reject отклоняет его с обязательным указанием причины. Это не транспортное подтверждение получения файла. Обе операции требуют confirmLegalAction: true, могут обработать весь пакет или выбранные attachmentIds и возвращают актуальное состояние документа. Если провайдер использует настраиваемый workflow, вызывающая система может передать action из массива availableActions, полученного через get.

availableActions нормализует только бизнес-смысл доступного перехода (accept, reject, send и т. д.). Идентификаторы этапа и действия остаются непрозрачными ссылками провайдера. Самостоятельное формирование криптографической подписи по-прежнему не смешивается с принятием документа и требует отдельного контракта.

Аннулирование отправленного документа является соглашением сторон, а не удалением записи. requestCancellation инициирует соглашение, acceptCancellation подтверждает полученный запрос, а rejectCancellation отклоняет его с обязательной причиной. Все три операции требуют явного confirmLegalAction: true. Удаление черновика, перемещение в корзину и необратимое уничтожение документа в эти операции не входят.

Локальная подпись разделена на три независимых шага. prepareSigning получает у оператора ЭДО хеши подготовленных вложений, trust.digitalSignature.create подписывает их в доверенной локальной среде, а completeSigning передаёт готовые подписи оператору и завершает выбранное действие. Закрытый ключ, PIN устройства и локальная сессия криптопровайдера никогда не входят в контракты.

Локальная доверенная среда

trust.signingEnvironment.inspect без изменения системы определяет, готово ли рабочее место к подписанию. Результат описывает компоненты через универсальные состояния: отсутствует middleware, устарел криптопровайдер, требуется лицензия, перезапуск или вмешательство поддержки. Названия конкретных продуктов остаются данными адаптера.

trust.signingEnvironment.configure устанавливает и настраивает только те компоненты, которые нужны найденному устройству и требованиям подписи. Операция требует confirmSystemChanges: true, но не принимает лицензионные ключи, пароли или PIN. Секреты получает локальная доверенная среда по собственным защищённым каналам.

trust.device.list отличает пассивные носители ключей от устройств со встроенной криптографией. trust.certificate.resolve автоматически подбирает сертификат по универсальным идентификаторам лица или организации, назначению ключа, сроку действия и требованиям к подписи. Например, российский ИНН передаётся как { scheme: "taxId", value: "...", jurisdiction: "RU" }, а не как отдельное поле контракта.

Каталог bridge содержит не бизнес-операции, а версионированный транспортный протокол локального исполнителя: manifest возможностей, сопряжение с HTTPS-origin приложения и конверт вызова операции. Grant всегда одноразовый или короткоживущий и выдаётся уже сопряжённым приложением. Это позволяет оставить пользовательский интерфейс в браузере, а криптографию и системную настройку выполнять в невидимом локальном сервисе.

bridgeOperationGrantClaimsSchema задаёт независимый от приложения формат допуска: компактный JWS с EdDSA, стандартными JWT claims и типом opsregistry-bridge-grant+jwt. Допуск связан с одним Bridge, origin, кодом операции, requestId и SHA-256 input, канонизированного по RFC 8785. Срок жизни и защита от повторного использования являются политикой локального Bridge.

Первичное сопряжение использует отдельные bridgePairingGrantHeaderSchema и bridgePairingGrantClaimsSchema. Самоподпись доказывает владение заявленным Ed25519-ключом, но доверие появляется только после подтверждения на локальной странице Bridge. Результат сопряжения различает ожидание подтверждения, успешное подключение, отказ и истечение срока.

Локальные возможности поставляются модулями. bridgeModuleManifestSchema описывает отдельный исполняемый артефакт для конкретной ОС, архитектуры и target triple: операции, разрешения, издателя, версию протокола, размер и SHA-256. Triple не ограничен перечислением и позволяет публиковать точные сборки для разных ABI Windows, Linux, macOS и других систем. Ed25519-подпись хранится отдельно и вычисляется над точными UTF-8 байтами файла manifest. Сам артефакт защищён подписанным хешем из manifest. Такой формат не зависит от языка реализации модуля и одинаково применим к онлайн-загрузке и офлайн-пакету.

bridgeModuleCatalogSchema описывает подписанный каталог релизов. Корневой ключ каталога делегирует ограниченные по времени ключи издателей, а каждая запись связывает операцию, точный target, manifest и бинарник. Монотонный sequence защищает от возврата к старому каталогу. Каталог хранит только HTTPS-адреса; приложение-потребитель передаёт Bridge код операции, но не URL исполняемого файла.

bridgeModuleInvocationSchema и bridgeModuleInvocationResultSchema задают небольшой JSON-протокол между core и отдельным процессом модуля. Он не зависит от Rust: модуль для конкретной ОС может быть реализован на любом языке, если принимает один вызов через stdin и возвращает один ответ через stdout с совпадающими версией протокола, invocation ID и кодом операции.

Публичный BridgeManifest разделяет установленные модули и доступность операций. Приложение видит, готова ли операция, может ли core установить её модуль или требуется устранить ошибку, но не передаёт Bridge URL исполняемого файла. Источник пакетов выбирает только доверенная конфигурация локального core.

Граница финансовых операций

finance.merchantPayment.create описывает входящую платежную попытку в пользу продавца/мерчанта: заказ в магазине, счет за услугу, QR СБП, карточный checkout и похожие acquiring-сценарии.

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

Если платежный провайдер умеет одновременно инициировать платеж и передавать данные для онлайн-кассы, используйте опциональное поле receipt в finance.merchantPayment.create. Для возврата или частичной отмены с фискализацией то же поле доступно в finance.merchantPayment.cancel.

Для всех операций используйте форму domain.resource.action, например finance.merchantPayment.create или finance.fiscalReceipt.create. Ресурс должен описывать бизнес-объект или bounded context, а не конкретного провайдера. Для вложенного ресурса допустимы дополнительные сегменты, например it.identity.token.refresh.

Примеры будущих отдельных финансовых поддоменов:

  • finance.payout.create для выплат клиентам или подрядчикам;
  • finance.bankTransfer.create для исходящих банковских переводов;
  • finance.invoice.create для выставления счета;
  • finance.billPayment.create для оплаты услуг внешнего поставщика.

Если меняется только метод оплаты внутри merchant checkout, например карта или СБП QR, это параметр finance.merchantPayment.*, а не отдельный провайдерский код операции.

Граница storage website операций

storage.object.* описывает работу с объектами внутри бакета: загрузить файл, удалить файл, получить список объектов. Опциональный websiteRedirectLocation в storage.object.uploadFile отражает object-level redirect header и зависит от поддержки конкретного S3-провайдера.

storage.website.* описывает конфигурацию static website hosting на уровне бакета: index/error documents, redirect-all requests и routing rules. Эти операции отделены от object-операций, потому что меняют настройки сайта целиком, а не отдельный файл.

Граница inventory

inventory описывает физический запас и его учётное состояние. Это не storage, который хранит файлы и S3-объекты, и не logistics, который оформляет и отслеживает перевозку.

  • товар идентифицируется через itemId, variantId, sku, gtin или externalIds, а не через ID конкретной таблицы;
  • количество всегда передаётся вместе с unitCode; дробные значения допустимы;
  • expirationDate хранит календарную дату годности партии в ISO-формате YYYY-MM-DD; это не timestamp и она не сдвигается между часовыми поясами;
  • мутации требуют idempotencyKey;
  • резерв меняет доступность, но не физический остаток;
  • transfer может ссылаться на shipment, оставаясь складской операцией;
  • adjust создаёт аудируемое исправление через дельту или фактический пересчёт и не удаляет историю.

Поля metadata и externalIds позволяют приложению сохранить локальный контекст, не добавляя его в универсальный код операции.

Граница documents и accounting

documents описывает передачу, получение, состояние, вложения и события электронного документа у оператора обмена. Он не определяет бухгалтерские проводки и не решает, как хозяйственное событие отражается в учёте.

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

pageToken и cursor намеренно непрозрачны для потребителя. Адаптер может хранить внутри них номер страницы, идентификатор события, документа или редакции конкретного провайдера.

Описание
TypeScript, Zod и OpenAPI-контракты универсальных операций OpsRegistry для приложений, адаптеров и локальных исполнителей
Конвейеры
0 успешных
0 с ошибкой
Разработчики