## Context **Revision:** 1. **Status:** согласован пользователем для подготовки Tasks; реализация не начата. Основание — [Proposal](proposal.md) и [Delta Specs](specs/manual-bank-cards/spec.md) с ответами пользователя Q1–Q9. Здесь определён способ реализации; проверяемое поведение и денежные правила принадлежат Delta Specs. WealthLedger уже использует React/Mantine, TanStack Query, NestJS, Prisma и локальную SQLite. Брокерские сведения приходят из T-Bank и временно кэшируются; пользовательские банковские операции станут долговременными данными. Их источник и правила доступа отличаются от брокерского журнала. Подход опирается на существующие границы: [контейнеры](../../../../docs/architecture/containers.md), [Money](../../../../docs/glossary.md#money), [backend data](../../../../backend/.ai/conventions/backend-data.md) и [frontend data](../../../../frontend/.ai/conventions/frontend-data.md). Ни внешний банк, ни новый сервис для ручного учёта не требуются. ## Goals / Non-Goals **Goals:** один владелец ручного учёта, точные денежные расчёты, согласованные переводы и возвраты, безопасные исправления и повторы, сохранение истории между запусками, два экрана в существующей визуальной системе. **Non-Goals:** новый стек хранения, универсальная бухгалтерская платформа, двойная запись, event sourcing, серверный кэш банковского учёта, отдельный изменяемый баланс или конвертация валют. Продуктовые границы перечислены в Proposal; этот Design их не расширяет. ## Decisions ### 1. Отдельная область ручного банковского учёта Backend получает один `BankCardsModule` с controller, сервисом сценариев, DTO и чистыми функциями расчёта. Сервис использует существующий `PrismaService`. Controller отвечает за транспорт, сервис — за владение данными, связи операций, проверки и транзакции. Generic repository, интерфейс для одной реализации и отдельные сервисы на каждый арифметический шаг не нужны. `JwtAuthGuard` и `request.user.sub` остаются источником локальной идентичности. Каждая карта, категория и операция выбирается с ограничением по владельцу; все дополнительные ссылки проверяются в том же сценарии. Неизменяемый владелец связанных записей не определяется из входного `userId`. `TbankAccessService` не используется: он устанавливает доступ через подключение и счета брокера. Ручные карты доступны без T-Bank. Замена или удаление брокерского подключения не удаляет и не инвалидирует ручную историю; logout по-прежнему очищает данные сессии в браузере. ### 2. Три сохраняемые модели | Модель | Данные и ответственность | | --- | --- | | `BankCard` | `id`, `userId`, имя, необязательные банк и последние четыре цифры, начальный остаток в копейках, дата начала учёта, `version`, `archivedAt`, неизменяемый fingerprint создания, служебные даты | | `ExpenseCategory` | `id`, `userId`, название, нормализованный ключ уникальности названия, необязательный неизменяемый `seedKey`, `version` | | `BankCardOperation` | `id`, `userId`, вид, `cardId`, необязательные `targetCardId`, `categoryId`, `originalExpenseId`, положительная сумма в копейках, дата операции, описание, `version`, `deletedAt`, неизменяемый fingerprint создания, служебные даты | Новые таблицы ссылаются на существующий `User`. Foreign keys сохраняют ссылочную целостность; ограничения уникальности защищают идентификаторы и категории при гонках. Совпадение владельцев, допустимые сочетания полей и финансовые зависимости проверяются сервисом внутри транзакции. Удаление карты или категории каскадом не применяется. При первой карте её создание и начальный набор пользовательских категорий сохраняются согласованно. `seedKey` позволяет повторно использовать начальные категории после переименования и удерживает роль категории по умолчанию. Если первая запись категории создаётся раньше карты, тот же сценарий сначала обеспечивает начальный набор. Чтение не создаёт данных, повторная инициализация не возвращает старые названия и не добавляет дубли. Перевод — одна `BankCardOperation`: `cardId` задаёт источник, `targetCardId` получателя. Две строки истории являются представлениями одной записи относительно выбранной карты. Изменение или удаление этой записи затрагивает оба эффекта; отдельного сохраняемого состояния «половина перевода» нет. Возврат ссылается на расход через `originalExpenseId`. Его карта проверяется по исходному расходу, а категория получается через эту связь. Собственная копия категории в возврате не хранится, поэтому исправление категории покупки не оставляет возвраты в прежней группе. Вид операции и ссылка возврата на исходную покупку после создания неизменны. ### 3. Копейки в хранилище, Money на внешней границе Начальный остаток и суммы операций сохраняются как `BigInt` целых копеек. Входные денежные значения проверяются по диапазону и точности Delta Specs до преобразования и записи. Расчёты используют `bigint`; результат не проходит через `Number`, `parseFloat` или SQLite `SUM` с ограничением 64-битного итога. Вычисленный баланс может выходить за диапазон отдельного сохраняемого значения. Публичный контракт остаётся Money `{ currency, value }`, где `value` — точная decimal string. Ограничение двух знаков относится к новому вводу RUB, а не к существующим брокерским Money. Frontend использует существующий [formatMoney](../../../../frontend/src/shared/lib/display.ts) и не складывает суммы. Уже есть [decimal helpers](../../../../backend/src/tbank/decimal.ts) и `MoneyDto` в [portfolio.dto.ts](../../../../backend/src/tbank/portfolio.dto.ts). Общие parsing/formatting и денежный DTO получают общий owner вне `tbank`; protobuf-преобразование остаётся у интеграции. Существующие потребители продолжают использовать ту же точную арифметику и тот же публичный Money-контракт. Новая денежная библиотека и две копии парсера не нужны. ### 4. Баланс и сводка вычисляются из первичных записей | Вид | Эффект на баланс | Эффект на месячную сводку | | --- | --- | --- | | Расход | Минус сумма у `cardId` | Расход по категории и дате покупки | | Пополнение | Плюс сумма у `cardId` | Не включается | | Перевод | Минус у источника, плюс у получателя | Не включается у обеих сторон | | Возврат | Плюс сумма у карты исходного расхода | Возврат по категории покупки и собственной дате | Backend выбирает минимальные необходимые поля и выполняет точный fold. Баланс использует начальный остаток и всю неудалённую историю карты; месячная сводка использует относящиеся к месяцу расходы и возвраты. Начальный остаток не создаёт синтетическое поступление. Архивная карта остаётся в исторической сводке «Все карты», чтобы архивирование не переписывало месячные итоги. Внутри одного ответа связанные первичные данные читаются согласованно. Если требуется несколько зависимых чтений, они используют одну read-транзакцию; независимые запросы интерфейса не получают общей транзакции ради внешнего вида. Frontend после подтверждённых изменений перечитывает связанные представления. Цена подхода — O(N) просмотр нужной ручной истории при расчёте. Для первой версии это проще второго сохраняемого баланса, его восстановления и инвалидации. В реализации у fold будет комментарий `ponytail:` с этим пределом и переходом к измеренной оптимизации при росте истории. Пагинация журнала не используется для ограничения финансового расчёта. Даты ручного учёта сохраняются как проверенные календарные `YYYY-MM-DD`, а не как фиктивный момент полуночи. Сегодня и текущий месяц определяются в Europe/Moscow; выбранный месяц — явный параметр. Это сохраняет календарный смысл введённого дня независимо от часового пояса браузера. ### 5. Транзакции защищают связанные изменения Проверка владельцев и активности всех старых и новых ссылок, проверка дат, финансовых зависимостей и запись выполняются в одной DB-транзакции. Изменение начальной даты карты проверяет всю зависимую историю, включая переводы, в которых карта является получателем. Сетевых запросов к банкам внутри транзакции нет. При создании, изменении или удалении возврата исходный расход участвует в условном обновлении версии внутри той же транзакции. Проверка оставшейся доступной к возврату суммы выполняется после получения права на изменение. При редактировании сумма остальных неудалённых возвратов складывается с новой суммой текущего: редактируемая запись не учитывается дважды. Так два возврата и одновременное уменьшение исходного расхода не обходят общий лимит, а изменение описания полного возврата остаётся допустимым. Неудачная проверка откатывает всё действие. Операции, редактирование метаданных и архивирование согласуются средствами SQLite-транзакций. Конфликт занятой базы или сериализации не превращается в частичный успех. Для кратковременного DB-конфликта допускается ограниченный повтор всей транзакции с тем же идентификатором намерения; пользовательский конфликт версии не повторяется скрыто. In-memory mutex и глобальный transaction manager не вводятся. Редактирование и удаление используют `version`: условное обновление по идентификатору, владельцу и ожидаемой версии. Устаревшая форма получает конфликт; frontend сохраняет ввод и выполняет повторный GET для получения актуальной записи, а не ожидает финансовый DTO внутри ответа об ошибке и не затирает изменение из другой вкладки. Начальный остаток и дата начала также защищены версией карты. Принцип уже используется в [AuthService](../../../../backend/src/auth/auth.service.ts) для conditional update; финансовые правила остаются у нового владельца. ### 6. Повтор создания не создаёт вторую карту или операцию При открытии формы новой карты или операции клиент создаёт UUID будущей записи и сохраняет его до определённого результата. Повтор после сетевой ошибки и общий auth-retry используют тот же UUID. Это идентификатор пользовательского действия, а не транспортный `X-Request-ID`. Запись хранит неизменяемый fingerprint нормализованного исходного ввода, рассчитанный стандартным `node:crypto`. Уникальный идентификатор защищён ограничением базы. Повтор с тем же владельцем, UUID и fingerprint не применяет создание второй раз; другой ввод с тем же UUID даёт конфликт. После проверки сессии и формы входа уже сохранённое намерение разрешается до проверки изменяемых условий нового создания: архива, текущей даты и доступного лимита возврата. Полный возврат с потерянным ответом не становится новым возвратом с нулевым лимитом, а поздний повтор создания архивированной карты возвращает её актуальное состояние. При коллизии создания сервис перечитывает результат и проверяет владельца и fingerprint, не полагаясь только на предварительный lookup. Редактирование не меняет fingerprint создания. Удаление оставляет tombstone с идентификатором и fingerprint: запоздалый повтор не воскрешает запись. Для уже изменённой записи возвращается её актуальное состояние; для удалённой — понятный безопасный отказ без нового сохранения. После определённого отказа пользователь может сознательно начать новое действие с новым UUID; автоматическая смена UUID при неопределённом исходе запрещена. В обеих моделях fingerprint остаётся неизменным; общего журнала команд или отдельной таблицы idempotency не требуется. Удаление операций логическое (`deletedAt`), не полное версионирование или бухгалтерский журнал аудита. Повтор удаления своей уже удалённой операции безопасен. Восстановление удалённых операций отдельным пользовательским сценарием в первую версию не добавляется. Архив карты обратим; для финансовых исправлений архивную карту сначала возвращают в активный список. ### 7. HTTP и frontend сохраняют существующие границы Новый API размещается под `/api/v1/bank-cards`. Внутри этой области находятся ресурсы карт, категорий, операций и месячной сводки. Одно представление перевода изменяется по идентификатору операции независимо от того, из какой карты открыта форма. Для выбора исходного расхода возврата доступно чтение расходов карты за всю историю, без ограничения месяцем открытого журнала. Runtime DTO валидируют форму, сервис — связанные ограничения. Чужие и отсутствующие ресурсы не раскрывают финансовых сведений. Ошибки используют существующий filter и стабильные коды для невалидного ввода, отсутствия ресурса, архива, конфликта версии, повторного намерения и зависимых возвратов. Конкретная исполняемая форма ответа принадлежит backend OpenAPI, а не дублирующей таблице DTO в спецификациях. Frontend получает типы из нового runtime OpenAPI через существующий [contract workflow](../../../../docs/development/runtime-openapi-verification.md). Используются `apiClient`/`apiQueryClient`, session-aware queries и общий auth middleware. Свой Bearer header, refresh loop или вторую систему запросов новая область не вводит. После успешного изменения данных инвалидируются банковские GET-запросы: список, детали, категории, страницы истории, выбор исходного расхода и все месячные сводки. Такой scope покрывает обе стороны перевода, изменение даты между месяцами и перегруппировку связанных возвратов. Брокерские кэши не затрагиваются. Оптимистический пересчёт денежных сумм не используется; ошибка mutation сохраняет прежние данные и введённую форму. Композиции принадлежат `pages/cards` и `pages/card`; каждая финансовая страница имеет container, VM и presentation views. Общие для обеих страниц banking queries и представления принадлежат `entities/bank-card`. Только реально повторяющиеся формы получают общего владельца; универсальный ledger, новый слой widgets и отдельный feature на каждое поле не нужны. ## UI/UX Applicability and Package **Applicability and scope:** `new/changed` — новый раздел «Карты», список, страница карты, формы карты/операций/категорий и месячная сводка. **Package revision:** 1. Пакет полностью находится в этом Markdown: схемы композиции, состояния, действия и адаптивность. Отдельные PNG/SVG и внешние макеты для согласования этой версии не требуются; self-hash не используется. **Screens, states and interactions:** | Экран | Состав и основные действия | | --- | --- | | `/cards?month=YYYY-MM` | Заголовок «Карты», «Добавить карту», список активных карт, переключатель показа архива, месячная сводка всех карт, «Категории» после появления первой карты | | `/cards/:cardId?month=YYYY-MM` | Крошки «Карты / Название», баланс, «Добавить операцию», изменение сведений/архив, сводка и журнал выбранного месяца | | Архивная карта | Явная отметка архива, доступные баланс/история/сводки, действие «Вернуть из архива»; финансовый ввод и исправления недоступны | ```text Карты [Добавить карту] [Повседневная 24 800 ₽] [Запасная 10 000 ₽] Баланс по внесённым операциям [Показать архив] Расходы по всем картам [Сентябрь 2026] Потрачено Возвращено После возвратов 8 500 ₽ 1 000 ₽ 7 500 ₽ Продукты … ₽ Кафе … ₽ Такси … ₽ [Категории] Карты / Повседневная Повседневная 24 800 ₽ [Добавить операцию] Баланс по внесённым операциям [Изменить карту] Расходы за месяц [Сентябрь 2026] Потрачено / Возвращено / После возвратов Категории и суммы Операции 12.09 Расход · Кафе −500 ₽ 11.09 Перевод на «Запасная» −2 000 ₽ 10.09 Возврат · Покупки +1 000 ₽ [Назад] [Далее] ``` Главная иерархия карты — название, баланс, действие, журнал. Начальные декоративные изображения банков не загружаются: название и необязательные последние цифры различают карты без внешнего каталога. Связанные ссылки перевода объясняют направление; одна карта не имитирует брокерский счёт. Сводка показывает расходы, возвраты и разницу отдельно. Отрицательный итог месяца или категории отображается со знаком и поясняется возвратами; процентный круговой график для таких величин не используется. Баланс остаётся текущим при смене месяца. Журнал упорядочен по дате операции, затем времени создания и идентификатору; переходы не обещают неизменяемый снимок истории между конкурентными изменениями. | Форма | Ввод и взаимодействие | | --- | --- | | Карта | Название, необязательные банк и последние четыре цифры, начальный остаток, дата начала. Пояснение: «Остаток до операций с выбранной даты». Валюта RUB фиксирована | | Расход | Сумма, дата, предвыбранная категория по умолчанию (изначально «Другое»), необязательное описание | | Пополнение | Сумма, дата, необязательное описание; без категории расходов | | Перевод | Карта-источник и другая активная карта-получатель, сумма, дата, описание; одно подтверждение для обеих сторон | | Возврат | Исходный расход за любой период, дата, сумма не выше доступной к возврату, описание; карта и категория берутся из покупки. При редактировании лимит равен сумме покупки минус остальные неудалённые возвраты | | Категории | Начальные и свои категории одним списком; добавление и переименование, без вложенности и удаления | Выбор возврата не ограничивается текущим месяцем: он показывает дату, описание/категорию покупки, сумму и оставшуюся доступную сумму. Добавление категории из формы расхода сохраняет уже введённые поля. Изменение операции сохраняет её вид; ограничения связанных возвратов объясняются у действия. Перед удалением операции подтверждение показывает сумму и последствия, для перевода — обе карты. При конфликте версии или зависимых возвратах форма сохраняет ввод и предлагает перечитать затронутую запись. Архивирование не удаляет историю и имеет явное обратное действие. **States and recovery:** | Состояние | Представление и восстановление | | --- | --- | | Initial loading | Структурный skeleton соответствующих областей, доступное состояние занятости, без фиктивных цифр и фокуса на декорациях | | Нет карт | «Добавьте первую карту» и действие добавления | | Пустой месяц | Нет операций/расходов за месяц, известные нулевые суммы, сохранённый текущий баланс | | Ошибка без данных | Понятное сообщение и повторная загрузка, без подмены данных нулями | | Фоновое обновление или его ошибка | Последние успешные данные остаются; рядом progress или предупреждение и retry | | Локальная ошибка сводки | Доступная карта и журнал остаются видимыми | | Invalid / pending формы | Ошибки полей без запроса; при отправке действия формы блокируются, включая закрытие | | Ошибка сохранения | Alert, сохранённый ввод и безопасный повтор того же действия; форма закрывается только после определённого успеха | | Чужая/отсутствующая карта | Нейтральное «Карта не найдена» и переход к списку без раскрытия чужих данных | Формы используют Mantine Modal, React Hook Form, Zod и FormProvider. Модальное окно удерживает фокус и возвращает его вызвавшему действию. Поля имеют видимые подписи, кнопки — доступные названия, суммы — знак и текст вида операции независимо от цвета. Продуктовые строки принадлежат существующему русскому i18n-словарю. **Responsive behavior:** на широком экране карты размещаются в две колонки, на узком — в одну; заголовки и действия переносятся. Формы становятся одноколоночными и помещаются по ширине экрана. Длинные названия и суммы не скрывают действия; журнал и формы не перекрываются нижней навигацией. Пятый пункт навигации «Карты» проверяется на ширине 320–375 px. Фокус, клавиатурное управление и reduced motion сохраняются. **Reused source and differences:** это новый пакет на текущей общей теме, а не повторное использование архивного UI-пакета. Поэтому immutable locator чужого change не требуется. Переиспользуются реальные элементы реализации: - [AppShell](../../../../frontend/src/shared/ui/AppShell.tsx) — один пункт «Карты» после «Счета» для desktop и mobile; текущие разделы сохраняются. - [ProtectedLayout](../../../../frontend/src/app/routes/ProtectedLayout.tsx) и [AppRoutes](../../../../frontend/src/app/routes/AppRoutes.tsx) — локальная сессия и два lazy-маршрута; крошки и заголовок документа расширяются у существующих владельцев. - [EntryCard](../../../../frontend/src/shared/ui/EntryCard.tsx) — представление строки операции с banking mapping. `LedgerEntryRow` и брокерские DTO напрямую не переиспользуются. - [AccountOperationsPage VM](../../../../frontend/src/pages/account-operations/ui/AccountOperationsPage.vm.ts) и [форма периода дохода](../../../../frontend/src/pages/account-income/ui/components/IncomePeriodForm.view.tsx) — примеры организации URL/форм, без cross-page imports. - [Дизайн-система](../../../../docs/development/design-system.md) — текущие светлая тема, семантические цвета и размеры. Новые шрифты, палитра и глобальная переработка оформления не вводятся. ## Delivery Map | Area | Status | Architectural outcome or N/A reason | | --- | --- | --- | | frontend | `affected` | Раздел карт, операции/категории/сводки, формы и состояния, generated client, локальная навигация и query invalidation | | backend | `affected` | Владелец ручного учёта, хранение, транзакции, точные расчёты, API и общие Money-компоненты без изменения брокерской семантики | | specs | `affected` | Новый контракт `manual-bank-cards` и SDD-артефакты этого change; текущие инвестиционные requirements сохраняются | | docs | `affected` | После поставки: область банковских карт, глоссарий, обзор продукта/навигация, расширение описания хранимых данных и восстановление | ### Contract Gate Decision | Gate | Status | Reason | | --- | --- | --- | | runtime-openapi | `required` | Новый backend API потребляется frontend; runtime document, snapshot и generated types должны совпадать | ## Solution Assessment Оценка обязательна из-за новых долговременных финансовых данных, транзакций и миграции. Выбирается существующая инфраструктура, с небольшой доменной реализацией внутри приложения. | Вариант | Fit, зрелость и безопасность | Эксплуатация, ограничения и миграция | Решение | | --- | --- | --- | --- | | Установленные Prisma + SQLite | Соответствуют локальному приложению; существующий DB provider, FK, unique constraints и транзакции | Без отдельного сервера; добавление таблиц сохраняет действующие данные; остаётся известная проблема migration engine | Использовать | | Другой сервер БД, например PostgreSQL | Зрелые средства конкурентной записи; текущая нагрузка не требует смены SQLite | Новый сервис, конфигурация, резервирование и миграция всей базы | Не добавлять в эту итерацию | | Browser storage | Нативное средство, но не соответствует сохранению серверных пользовательских данных и доступу через существующую сессию | Появляется второй источник истины и отдельная миграция из браузера | Не использовать для финансовой истории | | Prisma Decimal поверх SQLite | Удобное имя типа не доказывает точный путь хранения | В установленном adapter наблюдалось преобразование Decimal через `parseFloat`; BigInt сохраняет целые значения | Хранить копейки в BigInt; проверить реальный roundtrip | | Изменяемый cached balance или event-sourced ledger | Возможны при иных объёмах и требованиях аудита; сейчас добавляют второе состояние или инфраструктуру событий | Дополнительные восстановление, проекции, invalidation и миграционные обязательства | Вычислять из трёх простых моделей | Наблюдение adapter относится к установленной зависимости, зафиксированной в [backend package-lock](../../../../backend/package-lock.json), и её подключению в [PrismaService](../../../../backend/src/prisma/prisma.service.ts). Это не заменяет будущую проверку сохранения значений больше `2^53` через реальную БД и не требует изменения версии зависимости на этапе Design. ## Risks / Trade-offs - Пропущенные ручные движения приводят к расхождению с банком. Отрицательный учётный баланс допустим; приложение не проверяет банковский доступный лимит. - Возврат прошлой покупки может дать отрицательный месячный итог. UI показывает расходы и возвраты отдельно, а не скрывает это округлением или обнулением. - Редактирование истории меняет прошлые сводки. Здесь нет неизменяемого бухгалтерского аудита; soft deletion предотвращает повторное применение удалённой записи, но не является полной историей всех её редакций. - Точный fold имеет O(N) стоимость. Нужную оптимизацию выбирают после измерения фактической истории, сохраняя одного владельца расчёта. - В [текущих ограничениях проверки](../../../../docs/development/verification.md#известное-ограничение-сквозной-проверки) зафиксирован `Schema engine error` Prisma 7.9.1/Node 22 на новой SQLite. В этой фазе он не воспроизводился и не признан устранённым. Финансовую поставку нельзя признать проверенной без реальной DB/migration границы. - Существующая ValidationPipe не доказывает обещанные структурированные field errors — это отмечено в [HTTP API](../../../../docs/architecture/http-api.md#error-contract). Формы имеют локальную валидацию и безопасный общий ответ об ошибке; исполняемое поведение нового API проверяется на настроенном HTTP-приложении. ## Migration Plan Сначала подтверждаются сохранность действующей базы и применимость известных ограничений миграций на изолированной копии. Backup включает согласованный снимок SQLite; ключ T-Bank сохраняется отдельно и не выводится в отчёты. Работающая база и её WAL не копируются несогласованными частями: используется корректный SQLite backup либо остановленное приложение. Изменение схемы additive: три таблицы, индексы и связи с `User`. Существующие строки User, Session и TbankConnection не переписываются. Применённые migrations не редактируются; schema reset и обход migration history не используются. Категории создаются для владельца при первом вводе, без массового изменения всех существующих профилей. Новый backend и его публичный контракт готовятся до включения нового frontend-потребителя. Перенос общих Money-компонентов сохраняет действующий контракт брокерских сценариев. Документация описывает поставленный результат после проверки, а не объявляет будущие возможности готовыми. Если migration engine мешает проверить additive-переход, сначала решается это подтверждённое препятствие в допустимых границах. Unit и OpenAPI success не подменяют миграцию на изолированной реальной базе. Условие пересмотра известного blocker сохраняется у [владельца проверки](../../../../docs/development/verification.md#известное-ограничение-сквозной-проверки). ### Rollback Contract | Field | Decision | | --- | --- | | Status | `required` | | Trigger | Регрессия входа/действующих данных, неверные финансовые результаты, нарушение изоляции владельцев или неуспешная миграция | | Actions | Прекратить новые записи, сохранить актуальную базу; вернуть совместимую предыдущую версию приложения, сохранив добавленные таблицы. Не выполнять destructive down-migration | | Data safety | Проверенный backup до миграции и актуальный снимок после остановки записей сохраняются отдельно. Старый backup не восстанавливается поверх новой истории как штатный rollback: это потеряло бы введённые операции | | Verification | На disposable копии подтверждаются сохранность действующих пользователей/сессий/подключения, совместимость старого приложения с расширенной схемой, доступность новых записей после возврата новой версии и восстановление согласованного снимка | Более детальная последовательность доставки и команды проверок принадлежат следующему артефакту Tasks. Этот документ не разрешает менять пользовательскую базу или начинать реализацию до согласования подготовленного решения.