Skip to content

ADR-019 — Версионирование определений и регистрационный handle

Поле Значение
Статус Принято
Версия v.1
Дата 2026-06-28
Владелец Руслан Габитов
Уточняет SAD-001 v.1 Vision & Architecture

EN-оригинал — канонический: ADR-019-definition-versioning.md. Этот файл — его перевод (twin).

Принято — приземлено сопровождающими SRD (SRD-031.A версионирование, SRD-031.B конкурентность реестра). Решает версионирование определений: регистрация процесса порождает неизменяемый, версионированный снимок, адресуемый регистрационным handle, вместо единственной случайно-изменяемой регистрации с ключом по id процесса. Повторная регистрация того же id процесса даёт новую версию; старые версии продолжают крутить уже-запущенные на них экземпляры. Последняя версия всегда владеет автостартом — регистрация более новой версии вытесняет стартеры предыдущей, а снятие последней версии повышает теперь-новейшую обратно до live. Модель процесса остаётся изменяемой после регистрации (без заморозки) — движок изолирован от неё снимком, взятым в момент регистрации. Конкретный API редактирования модели (Unlink, Remove, Clear, …) — это соседний ADR о rich-редактировании; дисциплина конкурентности реестра Thresher (аудит §2.6) принадлежит сопровождающему SRD.

1. Контекст и проблема

SAD-001 v.1 формулирует входной контракт движка прямо: Snapshot — это «неизменяемое, валидированное представление определения процесса. Движок принимает Snapshot, а не изменяемую модель.» Сегодня движок чтит неизменяемую половину лишь по соглашению, а идентичность регистрации неоднозначна. Отсюда три пробела.

1.1 Идентичность регистрации неоднозначна и молча единична

Thresher.RegisterProcess принимает *process.Process, строит снимок и хранит его с ключом по id процесса. Thresher.StartProcess затем принимает этот id (string) и запускает тот снимок, что хранится под ним. Накладываются две проблемы:

  1. Повторная регистрация — молчаливый no-op. Регистрация процесса, чей id уже присутствует, сохраняет первую регистрацию и отбрасывает вторую («побеждает первая регистрация»). Пользователь, отредактировавший процесс и перерегистрировавший его в ожидании, что движок будет крутить новую форму, получает старую — без ошибки и без сигнала. Рассинхрон проявляется лишь как неверное поведение в рантайме.

  2. Ключ старта — это собственный id процесса, а не регистрационная квитанция. Вызывающий адресует регистрацию, доставая id из объекта модели. Нет первоклассного токена, говорящего «именно это зарегистрированное определение». Когда со временем легитимно может быть зарегистрировано более одной формы процесса, «запусти процесс с id X» не может сказать, какой именно X.

1.2 Снимок на самом деле не изолирован от модели — молчаливая утечка

Модель процесса изменяема после регистрации. Её публичная поверхность позволяет вызывающему продолжать добавлять и перевязывать элементы уже после взятия снимка, и ничто это не отвергает и даже не замечает. Хуже того, снимок не делает собственную копию графа узлов определения в момент регистрации: он разделяет объекты узлов модели по ссылке и клонирует их лишь позже, по разу на каждый запущенный экземпляр. Следствие — класс молчаливых, форм-зависимых повреждений:

  • Структурные правки невидимы. Узел или sequence flow, добавленный в модель после регистрации, никогда не входит в уже-взятый снимок, так что запущенный экземпляр крутит определение, которое вызывающий уже не видит в собственном объекте.
  • In-place правки протекают назад. Перевязка или мутация существующего разделяемого узла дотягивается до снимка через разделяемый указатель, так что следующий экземпляр, рождённый из этого снимка, молча подхватывает изменение — и делает это, конкурируя с любым экземпляром, уже клонирующим снимок, без лока на этом пути.

Это находка архитектурного аудита §3.3 («валидация и изменяемость модели процесса»): модель может быть мутирована после взятия снимка и во время выполнения. Предложенное аудитом средство — заморозить модель после первого снимка. Этот ADR достигает той же безопасности противоположным, более компонуемым путём — см. §2.3 и §4.1.

1.3 Прецедент — версионирование определений в Camunda

Camunda (зрелый BPMN-движок) разрешает ровно эту неоднозначность версионированием определений, и её модель хорошо понятна BPMN-практикам:

  • Передеплой изменённого определения под тем же ключом создаёт новую версию; версии инкрементируются как целые числа. «Если вы передеплоите изменённое определение процесса, вы получаете новую версию в базе данных.»
  • Запущенные экземпляры не затрагиваются: «Запущенные экземпляры процесса продолжат выполняться в той версии, в которой были запущены. Новые экземпляры процесса будут выполняться в новой версии — если не указано явно иное.»
  • Старт детерминированно выбирает версию: по ключу запускается «экземпляр последней задеплоенной версии»; по конкретному id запускается именно эта версия.
  • Подписки автостарта переходят на последнюю версию: «При деплое новой версии определения процесса signal-подписки предыдущей версии отменяются» — так что только последняя версия запускает новые экземпляры по message/signal start trigger; старые версии лишь доделывают свою незавершённую работу.

Этот ADR перенимает эту модель, наложенную на словарь снимков/instance-стартеров gobpm. (SAD-001 уже числит платформенное версионирование как отслеживаемый эпик; это — in-memory фундамент регистрации под него, а не durable-история деплоя/миграции, которая остаётся отложенной — §2.8.)

2. Решение

2.1 Регистрация версионируется по id процесса (ключ BPMN)

Ключ версионирования — это id процесса, стабильная идентичность элемента BPMN. Вызывающий фиксирует его явно (атрибут BPMN id; в gobpm — опция id процесса). Две регистрации с одинаковым id — это две версии одного логического определения; версии инкрементируются как целые (v1, v2, …) на каждый ключ в порядке регистрации. Номер версии монотонен и никогда не переиспользуется на протяжении жизни ключа: удаление версии (§2.5) оставляет её номер в отставке, так что последовательность может нести разрывы (v1, v3, …), и более поздняя регистрация всегда берёт свежий бóльший номер, а не заполняет дыру. Номер версии поэтому однозначно именует одно определение всё то время, что живёт ключ.

Процесс, зарегистрированный без явного id, сохраняет дефолт gobpm — сгенерированный уникальный id — и потому является собственным singleton-ключом на версии 1. Это корректный исход: у анонимного определения нет разделяемой идентичности, чтобы образовать с кем-то линию версий, а молчаливое слияние двух несвязанных анонимных процессов было бы хуже, чем трактовать каждый как самостоятельный.

id выбран вместо name процесса намеренно. BPMN перекрёстно ссылается на элементы по id — каждый (ref) в модели есть id-ссылка на другой элемент — тогда как name — простой опциональный атрибут без референциальной роли. id поэтому есть идентичность элемента, а name — отображаемый текст; ключевание версий по отображаемому имени схлопнуло бы в одну линию два по-настоящему разных процесса, случайно делящих один лейбл. (См. §4.2 про альтернативу с ключом по имени и почему она отклонена.)

2.2 RegisterProcess возвращает регистрационный handle

RegisterProcess возвращает значениерегистрационный handle — а не только ошибку. Handle — первоклассная квитанция на именно эту зарегистрированную версию. Он несёт (концептуально) ключ (id процесса), целочисленную версию и непрозрачный id регистрации, и именно его вызывающий передаёт, чтобы запустить или снять эту версию.

reg, err := th.RegisterProcess(p)   // reg именует конкретную (key, version)
inst, err := th.StartProcess(reg)   // запускает ИМЕННО ту версию, однозначно
err = th.Unregister(reg)            // снимает ИМЕННО ту версию

Это убирает неоднозначность §1.1 в корне: вызывающий больше не лезет в модель за id в надежде, что движок ещё держит ту форму, которую он имеет в виду — он держит квитанцию на точную зарегистрированную им версию.

2.3 Версия заморожена в момент регистрации — изоляция снимка

Взятие снимка в момент регистрации глубоко копирует граф узлов определения, так что снимок владеет собственными узлами и потоками, полностью независимо от объекта модели. С этого мгновения версия по-настоящему неизменяема: позднейшие правки модели — структурные или in-place — не трогают ничего из того, что держит движок, а запущенный экземпляр всегда крутит ту форму, что существовала на момент его регистрации.

Это и есть механизм, позволяющий модели оставаться изменяемой без заморозки из аудита. Изоляция, а не запрет — вот что делает «движок принимает неизменяемый Snapshot» (SAD-001) на самом деле истинным, и это закрывает утечку §1.2 / аудит §3.3 и сопутствующую ей гонку данных по построению: больше нет ни одного разделяемого изменяемого объекта между редактирующей goroutine вызывающего и клонирующим путём движка. (Неизменяемая per-definition конфигурация, которую экземпляр никогда не переписывает — напр. объявленные свойства — может по-прежнему разделяться по ссылке; копировать обязан только изменяемый граф. Per-instance клон, который мутирует выполняющийся экземпляр, не меняется и по-прежнему берётся из замороженного снимка.)

2.4 Старт — три режима адресации

Вызывающий может именовать запускаемую версию тремя способами, от наиболее к наименее специфичному:

  • По handleStartProcess(reg) запускает ту самую версию, что именует handle. Канонический, однозначный путь, и единственный, не требующий поиска.
  • По ключ + версияStartVersion(key, version) запускает именно эту целочисленную версию ключа, либо ошибётся, если ключ или версия неизвестны. Он адресует по номеру версии, а не по позиции в слайсе, поэтому остаётся корректным сквозь разрывы, которые может оставить удаление (§2.5) — повторный запуск v3 после того, как v2 была вычищена, резолвится в v3, а отставная v2 ошибётся. Это адресует конкретную историческую версию без удержания её handle (человекочитаемая (key, version), а не непрозрачный id регистрации).
  • По ключу (последняя)StartLatest(key) запускает последнюю зарегистрированную версию ключа, либо ошибётся, если ключ неизвестен. Это зеркалит частый случай Camunda «просто запусти текущую», так что повседневный вызов не обязан протягивать handle или отслеживать номер версии.

Все три возвращают per-instance handle наблюдения, который StartProcess возвращает сегодня (без изменений — отличный от регистрационного handle из §2.2). Точные имена/сигнатуры методов финализированы в сопровождающем SRD; концептуально движок предоставляет старты, адресованные по handle, по (key, version) и по последней-в-ключе.

Поскольку удаления могут оставить разрывы в последовательности версий ключа (§2.5), движок также предоставляет путь обнаружения: перечисление зарегистрированных версий ключа (возвращая их handle), чтобы вызывающий мог увидеть, какие версии существуют — и выбрать одну для старта или снятия — а не угадывать номер. Перечисление — read-only обнаружение, регистрационный аналог instance/starter-вью из SRD-019.

2.5 Автостарт следует за последней версией — вытеснение при регистрации, повышение при удалении

Для процесса, зарегистрированного в режиме автоинстанцирования (message/signal start trigger порождает экземпляры — ADR-015 v.1), регистрация более новой версии того же ключа передаёт ей автостарт: instance-стартеры предыдущей версии демонтируются с шины событий движка, а на их место регистрируются стартеры новой версии. Только последняя версия порождает новые экземпляры по триггеру; экземпляры, уже выполняющиеся под старыми версиями, доходят до завершения на собственных замороженных снимках.

Это прямой аналог Camunda-евского «signal-подписки предыдущей версии отменяются» (§1.3), и это разрешает иначе-острый угол «реагируют ли все зарегистрированные версии на одно входящее сообщение?» — нет; триггером владеет последняя версия. Версии manual-старта не регистрируют стартеров, так что вытеснять нечего.

Вытеснение симметрично при удалении: снятие последней версии повышает теперь-новейшую оставшуюся версию — её стартеры регистрируются в свою очередь, так что она становится live автостарт-версией. Инвариант последняя зарегистрированная версия есть live автостарт-версия поэтому держится всегда, в обе стороны. Это зеркалит поведение Camunda при удалении деплоя (подписки start-event предыдущей версии реактивируются) и даёт пользователям прямой рычаг для реактивации автостарта более ранней версии: удаляй более поздние версии, пока нужная снова не станет последней. Предыдущая (не-последняя) версия остаётся запускаемой, но только вручную через StartVersion(key, n) — она не держит hub-подписок, пока существует более новая версия.

Удаление приходит в двух гранулярностях, названных по их охвату: удаление одной версии (по handle) против удаления всего процесса — каждой версии ключа — за одну операцию. Процесс — это всё ключёванное определение; версия — один его снимок, поэтому массовая операция несёт имя Process, а одно-версионная явно есть удаление version. Обе оставляют запущенные экземпляры нетронутыми (они владеют своими снимками); демонтируются когда-либо лишь стартеры live-последней. Удаление-всего-процесса также отправляет в отставку счётчик версий ключа, так что более поздняя регистрация этого id рестартует с v1.

Корреляция/дедуп незавершённого диалога (ADR-016 v.1) остаётся ключёванной внутри линии порождающей версии; поскольку автостартует только последняя версия, create-or-join остаётся когерентным. Межверсионная корреляция (диалог, начатый под v1 и маршрутизируемый в экземпляр v2) не является целью здесь — §2.8.

2.6 Модель остаётся изменяемой — редактирование есть соседнее решение

Модель процесса не замораживается регистрацией. Вызывающий может продолжать строить или править её и перерегистрировать, чтобы отчеканить следующую версию. Этот ADR коммитится к этому принципу; он намеренно не специфицирует сами операции редактирования. Первоклассная поверхность редактирования модели — отвязка потоков, удаление узлов, очистка процесса и правила валидации/упорядочивания, которые они подразумевают — это отдельное решение с собственным object-model рассуждением, и оно принадлежит соседнему ADR о rich process-model редактировании. Изоляция снимка (§2.3) — это предусловие, делающее такое редактирование безопасным для независимого приземления: как только регистрация копирует граф, пострегистрационное редактирование не может потревожить ни одну взятую версию.

2.7 Дисциплина конкурентности версионированного реестра — принадлежит SRD

Введение версий переформовывает реестр движка из «один снимок на id» в «упорядоченное множество версий на ключ, с выделенной последней», и переписывает сами методы регистрации/старта/снятия, чью дисциплину локов архитектурный аудит пометил как хрупкую (аудит §2.6: корректность, держащаяся на комментарии, «release before launch … re-acquire», refactor-враждебный инвариант). Поскольку реализующая работа трогает ровно эти методы, чистая дисциплина конкурентности, о которой просит аудит — состояние движка как atomic-значение и явное, задокументированное разделение между lock-held и lock-free секциями — перенимается как часть приземления этого ADR, фиксируется как реализационное решение в его выделенном SRD о конкурентности, отдельном от SRD о версионировании. Этот ADR фиксирует что (версии, handle, изоляция, последняя-вытесняет); SRD владеет как внутренностей реестра и отправляет аудит §2.6 в отставку там.

2.8 Не-цели и вне области (каждая — со своим именованным домом)

  • Durable / persistent версионирование и миграция. Версии здесь живут только в памяти работающего движка. Персистенция деплоев, регидратация экземпляров на сохранённую версию и миграция live-экземпляров между версиями остаются отложенными платформенными эпиками (SAD-001) — будущим решением Persistence & State.
  • API редактирования модели. Unlink / Remove / Clear и их семантика — соседний ADR о rich-редактировании (§2.6).
  • Механика переписывания конкурентности реестра. Выделенный SRD о конкурентности (§2.7); этот ADR не несёт file/line.
  • Межверсионная корреляция и version-pinned маршрутизация сообщений. Диалог, охватывающий версии (§2.5) — вне области; пересмотреть с durable-историей.
  • Version-теги / семантические version-лейблы. Camunda-евский versionTag «только для тегирования и не влияет ни на старт … ни на поведение.» Целочисленного упорядочивания версий здесь достаточно; именованные теги — поздняя эргономическая добавка, если попросят.

3. Последствия

Положительное.

  • Неоднозначность §1.1 исчезает: handle именует ровно одну версию; «запусти то, что я только что зарегистрировал» однозначно, а повторная регистрация — это осмысленная операция (новая версия), а не молчаливый no-op.
  • Утечка §1.2 / аудит §3.3 и её гонка закрыты по построению через изоляцию (§2.3), без ограничения пользователя — чтя принцип проекта «компонуй, не ограничивай».
  • Поведение совпадает с моделью, которую BPMN-практики уже знают (Camunda), снижая кривую обучения.
  • Несколько форм определения могут безопасно сосуществовать: долго-крутящиеся экземпляры v1 доходят, пока v2 берёт новые старты.

Отрицательное / издержки.

  • Изменение контракта. RegisterProcess получает возвращаемое значение; StartProcess и одно-версионный UnregisterVersion принимают handle, со StartVersion(key, version) / StartLatest(key) / UnregisterProcess(key) (удаление-всего-процесса) как key-адресованными путями. Прежние, до версионирования, места вызова должны перейти на handle. Это приемлемо до 1.0 и есть смысл изменения.
  • Регистрация больше не идемпотентна. Повторная регистрация теперь добавляет версию. Вызывающие, перерегистрировавшие защитно, должны прекратить — или принять рост числа версий.
  • Издержка копирования на регистрацию. Глубокое копирование графа в момент регистрации добавляет работу и память на версию (ограничено размером определения, оплачивается раз на регистрацию, а не на экземпляр).
  • Неограниченное накопление версий, если вызывающий перерегистрирует в цикле без снятия. Митигации (retention/pruning) отмечены в §5; движок не накладывает потолок в этом ADR.

4. Рассмотренные альтернативы

4.1 Заморозить модель после первого снимка (предложение аудита)

Пометить процесс неизменяемым при регистрации; отвергать позднейшие правки ошибкой. Отклонено как основной механизм: оно ограничивает пользователя, чтобы решить баг изоляции движка, и конфликтует с направлением rich-редактирования (§2.6) и принципом проекта «компонуй, не ограничивай». Изоляция снимка (§2.3) достигает той же безопасности, сохраняя модель редактируемой, что строго более компонуемо. (Заморозка всё ещё может быть предложена позже как opt-in утверждение для вызывающих, кто хочет запечатанный builder, но это не дефолт и не требуется для безопасности.)

4.2 Ключевать версии по имени процесса

Использовать отображаемое name модели как ключ версионирования (эргономически самое «Camunda-ощущение», поскольку имя присутствует всегда). Отклонено: в BPMN имя — это неидентифицирующий отображаемый текст; ключевание по нему схлопывает два несвязанных процесса, делящих лейбл, в одну линию версий, и молча. id — это идентичность стандарта и безопасный ключ (§2.1). Эргономика возвращается тем, что вызывающему позволено задать короткий, стабильный id.

4.3 Изоляция снимка без handle (только глубокое копирование)

Починить только §1.2 — заставить снимок копировать граф — но сохранить одно-ключёванную, id-адресованную, идемпотентную регистрацию. Отклонено как недостаточное: оно убирает утечку и гонку, но пострегистрационная правка тогда молча игнорируется (снимок заморожен, а повторная регистрация всё ещё no-op), что и есть путаница §1.1, которую поднял пользователь. Изоляция необходима, но не достаточна; именно handle + версионирование делает намерение пользователя выразимым.

4.4 Непрозрачный id регистрации без группировки по ключу

Возвращать непрозрачный id на регистрацию без понятия разделяемой линии ключ/версия (каждая регистрация — остров). Отклонено: оно дезамбигуирует старт, но теряет «последняя версия этого определения», не может выразить вытеснение автостарта (§2.5) и отбрасывает хорошо понятную ментальную модель Camunda без выигрыша.

5. Рекомендации Enterprise-готовности

  • Политика удержания версий. Предоставить (в сопровождающем SRD или follow-up'е) способ перечислить версии ключа и вычистить вытесненные, как только их экземпляры сольются, чтобы долгоживущий движок, часто перерегистрирующий, не накапливал мёртвые снимки. Выводить счётчики через существующую поверхность observability.
  • Observability версионирования. Логировать при регистрации назначенную (key, version) и, при вытеснении автостарта, версию from→to и перенос starter-подписки, чтобы операторы видели, какая версия live и почему триггер породил именно ту версию.
  • Руководство по явному id. Задокументировать, что durable, намеренное версионирование требует стабильного id процесса; анонимный (auto-id) процесс всегда singleton v1. Рассмотреть debug-level предупреждение, когда две регистрации делят имя, но различаются id (вероятная ошибка «я имел в виду версионировать это»).
  • Путь устаревания к durability. Держать словарь in-memory модели (ключ/версия/handle) согласованным с будущей durable-историей деплоя, чтобы persistence-ADR мог перенять его без переименования публичной поверхности.

6. Ссылки

  • SAD-001 v.1 Vision & Architecture — родитель: Snapshot как неизменяемый входной контракт движка («Движок принимает Snapshot, а не изменяемую модель»), и отслеживаемый платформенный эпик версионирования, под который это закладывает in-memory фундамент.
  • ADR-009 v.1 Per-instance node graph — модель снимок→per-instance клон, которую это расширяет; копия графа в момент регистрации из §2.3 — недостающий первый шаг той же истории изоляции.
  • ADR-015 v.1 Event-triggered instantiation — definition-level instance-стартеры, чей перенос по правилу последняя-вытесняет (§2.5) решается здесь; manual-start режим, не регистрирующий ни одного.
  • ADR-016 v.1 Message correlation — резолюция create-or-join, остающаяся когерентной под версионированием, поскольку автостартует только последняя версия (§2.5).
  • ADR-001 v.6 Execution model — экземпляры, треки и per-instance жизненный цикл, который старые версии продолжают крутить после вытеснения.
  • ADR-013 v.1 Instance observability — read-only поверхность handle/обнаружения, которую расширяет observability версионирования (§5).
  • Архитектурный аудит 2026-06-11 — §3.3 (изменяемость модели после снимка; этот ADR разрешает её изоляцией, а не заморозкой) и §2.6 (хрупкая дисциплина мьютекса Thresher; отправлена в отставку SRD о конкурентности этого ADR, §2.7).
  • BPMN 2.0 — id у BaseElement — это атрибут, на который ссылаются другие элементы (docs/bpmn-spec/elements/foundation.md; перекрёстные ссылки спеки — это (ref) id-ссылки), тогда как name — простой опциональный атрибут без референциальной роли — основание выбора ключа в §2.1.
  • Camunda 7 manual — Process Versioning (версия при передеплое-по-ключу; запущенные экземпляры остаются; старт-по-ключу = последняя; versionTag не-поведенческий) и Signal Events («signal-подписки предыдущей версии отменяются») — прецедентная модель, перенятая в §1.3 / §2.5.

7. Открытые вопросы

Нет. Область: in-memory версионирование определений — версионированная регистрация с ключом по id процесса, регистрационный handle, изоляция снимка в момент регистрации, старт по handle / по ключ+версия / по последней-в-ключе, и автостарт по правилу последняя-вытесняет. API редактирования модели — соседний ADR о rich-редактировании; переписывание конкурентности реестра (аудит §2.6) — выделенный SRD о конкурентности этого ADR; durable версионирование, миграция, межверсионная корреляция и version-теги — именованные отложения (§2.8).

История документа

Версия Дата Автор Изменение
v.1 (Принято) 2026-06-29 Руслан Габитов Принято — концепция реализована; приземлена сопровождающими SRD: SRD-031.A (версионирование — регистрационный handle, монотонные версии на ключ, последняя-вытесняет + promote-on-removal, версии с пропусками, UnregisterVersion vs UnregisterProcess, Registrations(key)) и SRD-031.B (конкурентность реестра — атомарное состояние движка state с переходными Starting/Stopping, lock-free State/UpdateState/ensureStarted, критические секции реестра вынесены в …Locked-хелперы; снимает находку аудита §2.6). make ci зелёный, -race чист, diff-покрытие 100% на затронутых файлах, 18 примеров запускаются. API редактирования модели (ADR о rich-редактировании) и durable версионирование/миграция остаются отложенными.
v.1 (Черновик) 2026-06-28 Руслан Габитов Черновик. Версионирование определений: RegisterProcess возвращает регистрационный handle, именующий (key, version); ключ версионирования — это id процесса (идентичность BPMN, не отображаемое имя), анонимные процессы — singleton v1; снимок глубоко копирует граф в момент регистрации, так что каждая версия заморожена, а модель остаётся изменяемой без заморозки (закрывая утечку/гонку аудита §3.3 изоляцией); старт по handle (точно), по ключ+версия (конкретно) или по ключу (последняя); последняя-вытесняет автостарт держит последнюю версию единственным live-автостартером — регистрация более новой версии передаёт ей instance-стартеры, а снятие последней повышает теперь-новейшую оставшуюся версию обратно до live (Camunda-обоснованно, симметрично в обе стороны). Уточняет SAD-001 v.1; соседи ADR-009 v.1, ADR-015 v.1, ADR-016 v.1, ADR-001 v.6, ADR-013 v.1. API редактирования модели выделен в соседний ADR о rich-редактировании; дисциплина конкурентности реестра (аудит §2.6) — в выделенный SRD; durable версионирование/миграция/межверсионная корреляция/version-теги отложены.