Intekey WMS 2.0 / Изоляция заказчиков · AD-12…AD-21 · на 2026-11-09 1 / 9
Разбор архитектурного решения

Как Intekey WMS 2.0
отличает заказчиков друг от друга

Около двадцати заказчиков, одна кодовая база. В легаси заказчик — это список REQUIRE в его топ-модуле, зафиксированный при сборке. Дальше — почему так больше нельзя, почему привязка к ролям не решение, и что принято вместо.

Что это

Архитектурное решение на уровне эпика: десять инвариантов (AD-12…AD-21) поверх базового правила AD-2.

Статус

Принято и реализовано в Epic 23. Перевод живых контуров на новую модель — Epic 18.

Как удерживается

Правила проверяются автоматически при каждой сборке, а не только на ревью.

Как есть

Заказчик — это список модулей

Механизм простой, работающий, ему больше десяти лет. Он довёл легаси примерно до двадцати живых внедрений — и это надо признать прежде, чем его менять.

MODULE ClientC;
REQUIRE FusionWMS,
        Replenishment, ReplenishmentSelection, ReplenishmentPlan,
        ReplenishmentItemApi, InputControlReceipt,
        SelectionLotBanned;

Что даёт

  • Один взгляд — и видно, что у клиента включено
  • Всё в git, всё воспроизводимо
  • Нулевая стоимость рантайма

Чего не даёт

  • Изменение = пересборка и релиз контура
  • Своя сборка и своя версия у каждого заказчика, расходящиеся во времени
  • Ничто не мешает одному клиенту тянуть модуль другого
Почему разваливается · доказательство 1

Клиент перестал быть границей

Это не рассуждение — это коммит от 29 июля 2026.

#35261  FIX. Изменения по задаче 35261

  custom-logics/client-b/ClientB.lsf
  - SimpleCheckingTask, CheckingTaskUserShipment, ShipmentCheckingTaskClientA,
  + SimpleCheckingTask, CheckingTaskUserShipment,

  custom-logics/client-a/processing/checkings/ShipmentCheckingTaskClientA.lsf
  - удалён целиком

Топ-модуль Заказчика B подключал модуль Заказчика A. Граница, ради которой всё строилось, оказалась проходной: удаление модуля у одного клиента ломает другого.

Почему разваливается · доказательство 2

Граница дырява и в обратную сторону

Доработки под конкретного клиента часто вносятся не в его модуль, а прямо в общий/базовый модуль, от которого он зависит. Руководство разработчика легаси — записано как факт, а не как проблема

Тикет по клиенту «Заказчик C» правился в processing/selections/SelectionPlan.lsf — общем модуле. Значит по расположению кода нельзя сказать, чей он.

~20
заказчиков
2
механизма кастомизации
без правила, что куда
0
способов определить
владельца по коду
Отвергнутая альтернатива

«А давайте привяжем к ролям»

Гипотеза, с которой начался разбор. Отвергнута — и вот счёт, который пришлось бы заплатить.

ПоследствиеЧто произойдёт
Расхождение данныхОдин документ считается по-разному у кладовщика и у менеджера на одном складе. Не отображается — считается
Комбинаторный взрыв~20 заказчиков × 5 ролей ≈ 100 сущностей вместо ~20 профилей и 5 ролей
Аудит перестаёт читатьсяВ роли не отличить доступ от бизнес-правила; безопасник начинает читать бизнес-логику
Токен несёт логикуСмена правила расчёта = перевыпуск токенов

Но интуиция была верной наполовину. Роль остаётся — она раздаёт доступ. Она просто не меняет расчёт. Это AD-17

Принятое решение

Три ортогональные оси

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

Capability

Что умеет этот контур

Источник: профиль развёртывания
Момент: старт процесса

RBAC

Что позволено этому человеку

Источник: claims токена + справочник
Момент: запрос

Scope

К каким данным относится действие

Источник: склад/зона по топологии
Момент: запрос

разрешено = capability(контур) AND permission(роль) AND scope(данные)

Каждый сомножитель вычисляется независимо. Оси не выводятся одна из другой — именно это и сломалось в старой модели.

Что меняется на практике

Десять инвариантов

ADПравило
AD-12Тенант — константа развёртывания. Ни в DTO, ни в query, ни в заголовке, ни в claim, ни в сегменте пути
AD-13Типизированный scope: склад → контур → default. Уровень принадлежит настройке, а не вызывающему
AD-14Flyway = только схема. Значения — в смонтированном профиле, полный reconcile на каждом старте
AD-15Профиль состоит из capability. Частично включённая capability считается выключенной
AD-16Своя таблица → расширение у владельца; иначе флаг. Единица владения — донорский набор, а не один клиент
AD-17Роль раздаёт доступ, не меняет расчёт. Членство — у IdP, привязка — в профиле
AD-18Имя клиента в ядре валит сборку. Двумя механизмами, потому что одного не хватает
AD-19Клиентское приложение получает свои возможности с сервера и не выводит их само
AD-20Расширение мутирует ядро только через порты. Прямой доступ к таблицам запрещён
AD-21Переход живых контуров — явная процедура с записью «было → стало»
Как проверяем изоляцию

Правило, которое держит сборка

Правило «ядро не знает имён заказчиков» не полагается на внимательность ревьюера. Его соблюдение проверяется автоматически — четырьмя независимыми способами.

Архитектурные тесты

При каждой сборке проверяется, что ядро не обращается к коду конкретного заказчика, а расширение заказчика не обращается к данным ядра в обход портов.

Проверка исходников

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

Список исключений только сокращается

Известные исключения можно убирать, но нельзя добавлять: попытка дописать новое ловится тестом. На 2026-11-09 список пуст.

Заказчик — свойство развёртывания

Контур не стартует без кода заказчика и проверяет, что смонтированный профиль принадлежит именно ему. Код заказчика не принимается из запросов к API.

Осознанные компромиссы

Где решение можно оспорить

Четыре места, где решение принято сознательно и может быть оспорено. Остальное — следствия.

1. Полный reconcile на каждом старте

Ночная починка через API живёт до рестарта. Выбрано ради воспроизводимости before/after при кат-овере. Возражение: дежурному это неудобно.

2. Один клиент на контур

Соответствует сегодняшней реальности: свой сервер, своя БД, своя джоба деплоя. Возражение: закрывает дорогу к SaaS без переделки.

3. Уровень capability поверх флагов

При трёх заказчиках избыточен, окупается на двадцати. Возражение: лишний слой косвенности сейчас.

4. Красная сборка вместо отчёта

Правило в виде комментария на практике не удерживается. Возражение: сломанная сборка блокирует не того, кто виноват.

Полный перечень инвариантов — в документе «Intekey WMS 2.0 — целевая архитектура».