From 7f40372c3f67bb67cc77feaad17b61573c65f41e Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 20 Sep 2026 18:49:25 +0300 Subject: [PATCH] spec: define versioned deployment bundle --- .../.openspec.yaml | 2 + .../versioned-deployment-bundle/design.md | 179 ++++++++++++++++++ .../versioned-deployment-bundle/proposal.md | 40 ++++ .../specs/deployment-bundle/spec.md | 169 +++++++++++++++++ .../versioned-deployment-bundle/tasks.md | 68 +++++++ 5 files changed, 458 insertions(+) create mode 100644 openspec/changes/versioned-deployment-bundle/.openspec.yaml create mode 100644 openspec/changes/versioned-deployment-bundle/design.md create mode 100644 openspec/changes/versioned-deployment-bundle/proposal.md create mode 100644 openspec/changes/versioned-deployment-bundle/specs/deployment-bundle/spec.md create mode 100644 openspec/changes/versioned-deployment-bundle/tasks.md diff --git a/openspec/changes/versioned-deployment-bundle/.openspec.yaml b/openspec/changes/versioned-deployment-bundle/.openspec.yaml new file mode 100644 index 0000000..18f0603 --- /dev/null +++ b/openspec/changes/versioned-deployment-bundle/.openspec.yaml @@ -0,0 +1,2 @@ +schema: wealth-ledger-sdd +created: 2026-09-20 diff --git a/openspec/changes/versioned-deployment-bundle/design.md b/openspec/changes/versioned-deployment-bundle/design.md new file mode 100644 index 0000000..8cecc31 --- /dev/null +++ b/openspec/changes/versioned-deployment-bundle/design.md @@ -0,0 +1,179 @@ +## Context + +Согласован отдельный репозиторий `deploy` и команда `./deploy.sh ` по SSH. +Тег принадлежит комплекту, а не компоненту; соответствие пары хранится в +`release.env`. Решения приняты в обсуждении с владельцем и записаны в ADR 016 +репозитория docs; эти артефакты готовят реализацию, не подтверждают её выполнение. + +На сервере уже есть Compose project `wealthledger`, SQLite, постоянные ключи +и read-only mount `certs/tbank-ca.pem` через `TBANK_CA_CERT_PATH`. Внешний HTTPS +и Docker подготовлены. Образы публикуются в Nexus независимыми релизами. +`containerized-application-delivery` остаётся отдельным active change; +его runtime-контракт используется без редактирования или преждевременного archive. + +## Goals / Non-Goals + +**Goals:** небольшой комплект с воспроизводимой парой образов, одним entrypoint, +безопасным обновлением SQLite и понятным восстановлением существующего сервера. + +**Non-Goals:** provisioning, CI/CD автодеплой, новый registry, Kubernetes, +Ansible-инфраструктура, blue-green, замена БД, backup daemon, автоматическая +ротация копий, собственная система миграций или управление HTTPS. + +## Decisions + +### Комплект и постоянные ресурсы + +Минимальное содержимое `deploy`: `compose.yaml`, `release.env`, `.env.example`, +`deploy.sh`, README и одна исполняемая проверка сценариев. Compose использует +готовые frontend/backend images, migration запускается из backend image. +Production-комплект самодостаточен и не требует соседних checkout. +Существующие root Compose-файлы для development не удаляются этим change. + +На сервере checkout располагается отдельно от `data/`, `certs/`, `backups/` +и защищённого server env. Например, checkout — `/opt/wealthledger/deploy`, +остальное — соседние пути в `/opt/wealthledger`. Существующий data path +не переименовывается; пути bind mounts абсолютные. Ключи не генерируются +повторно. Root filesystem остаётся read-only, процесс non-root, backend +не получает host port; сертификат доверия монтируется read-only без отключения TLS. + +`release.env` содержит только полные versioned image references из Nexus; +`latest` исключён. Server env хранит настройки и секреты, но не выбирает пару. +Переменные окружения вызывающего shell не должны переопределять выбранные +images или Compose project. Env-файлы обрабатываются Compose как данные, +не выполняются shell через `source`. Полный config с секретами не печатается. + +### Исполнение выбранной версии + +Entrypoint получает тег только из настроенного доверенного `origin`, разрешает +его в commit и использует содержимое этого commit для всей операции, включая +версию deploy-логики. Полученный ранее тег не перезаписывается при расхождении. +Работа идёт из временного snapshot выбранного commit; запущенный скрипт и +операторские изменения checkout не перезаписываются переключением ветки. +Compose file, env files и project `wealthledger` задаются явно: временная +директория не должна создать второй project или изменить пути данных. + +До остановки выполняются проверки входов, ресурсов, конфигурации и загрузка +образов. Поддерживаемый target — один Linux Docker host; Git, Bash и `flock` +входят в перечисленные README предпосылки. Архитектура опубликованных образов +должна соответствовать серверу; multi-platform поддержку этот change не добавляет. + +### Обновление, повтор и состояние + +Один `flock` на установку исключает конкурирующие запуски. Не добавляется +сервис блокировок. Файл блокировки и минимальные служебные записи находятся +вне checkout, с ограниченными правами, без секретов. + +Нужны только запись последнего успешного релиза и маркер незавершённого +обновления: выбранный commit, фактические images, этап и расположение backup. +Маркер сохраняется до первого изменения runtime; успешная запись атомарно +обновляется только после готовности затронутых сервисов. При прерывании +маркер остаётся для диагностики; новый запуск требует ручного разбора по README. +Это не журнал всех deployments и не отдельная база состояний. +При первом переходе предыдущая версия описывается фактической парой images: +несуществующий тег прежнего комплекта не придумывается. + +План изменений сверяет желаемые images и Compose service configuration +с фактическими контейнерами. Для сравнения использовать native Compose +service hash и Docker labels, если их соответствие подтверждено проверкой. +Секреты не записываются в служебные файлы. Отсутствие достоверного baseline +не разрешает frontend-only shortcut: первый переход с серверного Compose +выполняется как полное обновление с backup. Успешная запись сама по себе +не заменяет проверку фактически работающих контейнеров. + +При обновлении API: stop backend → проверка отсутствия migration writer → +новый уникальный каталог backup → копия всего data directory, включая WAL/SHM → +одноразовая migration → backend readiness. Для существующей установки отсутствие +`wealthledger.db` — ошибка. Только явный первый запуск `--init` на пустом +каталоге без прежнего runtime допускает создание новой базы без backup. +Копия отмечается пригодной для восстановления только после успешного окончания +копирования; незавершённый каталог не выдаётся за готовый backup. + +Неизменный frontend не пересоздаётся; обновление только frontend не запускает +зависимости. При обновлении обоих компонентов новый frontend запускается после +готовности backend. Healthchecks используются из готовых images через Compose; +новый HTTP endpoint не нужен. Browser smoke через HTTPS остаётся отдельной +проверкой выпуска: container health не доказывает login/refresh. + +Backup failure и migration failure прекращают последовательность. При провале +readiness backend останавливается. Провал frontend не останавливает исправный +backend. Любая ошибка оставляет релиз неподтверждённым, текущую БД и backup +неизменёнными процедурой восстановления; диагностика не выводит secrets. + +### Выпуск и инструкция + +После публикации компонентов оператор меняет пару в `release.env`, проверяет +её на disposable установке и выпускает отдельный неизменяемый `vX.Y.Z` тег +`deploy`. Компонентные pipeline не связываются автоматическим publisher. +Первый комплект закрепляет реально доступные образы, не демонстрационные версии. +README описывает подготовку Docker/Git/registry/HTTPS, разрешения каталогов, +сохранение ключей, подключение действующего Compose project, install/update, +диагностику незавершённой операции, ручную очистку backups и recovery. + +## UI/UX Applicability and Package + +**Applicability and scope:** `N/A` — браузерный интерфейс и его состояния +не меняются; оператор использует терминал и README. Макеты не нужны. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | `N/A` | Готовый image переиспользуется, UI и pipeline не меняются | +| backend | `N/A` | Готовые runtime, healthcheck и migrations переиспользуются | +| specs | `affected` | Контракт deployment-bundle и план его проверки | +| docs | `affected` | ADR 016 и обновлённый эксплуатационный маршрут после реализации | +| deploy | `affected` | Самодостаточный комплект, сценарии проверки и README | +| root | `affected` | Только навигация README к production-комплекту; development Compose не меняется | + +Уточнение старого шаблона: обязательные четыре области сохранены; добавлен +реальный repository owner `deploy`, явно выбранный пользователем, и `root` +для необходимой поправки workspace-навигации. В Tasks используются `[deploy]` +и `[root]`, а не фиктивный `[backend]` или `[workspace]`. +Это локальное расширение перечня owners для данного change; глобальная +переработка SDD schema и process specs в scope не входит. Native structural +validation не доказывает соответствие этому уточнению или semantic approval. + +## Solution Assessment + +| Вариант | Fit, зрелость и безопасность | Стоимость и ограничения | +| --- | --- | --- | +| Только Docker Compose | Зрелый runtime, зависимости, healthchecks и выбор images уже покрыты | Не выполняет stop/copy/migrate и не исключает две операторские операции | +| Bash + Git + Compose + flock | Native инструменты владеют загрузкой, запуском и блокировкой; shell связывает существующий безопасный порядок | Небольшой branching требует проверок ошибок и прерываний; подходит одному Linux host | +| Ansible | Зрелый механизм управления серверами, но backup/migration порядок всё равно надо определить | Добавляет control environment, inventory и роль при уже подготовленном одном сервере | + +Выбран второй вариант. Чистого Compose недостаточно для согласованной копии +при остановленной записи; отдельный provisioning stack не соответствует +согласованному отсутствию управления серверной инфраструктурой. Новый package +manager, framework или сторонняя runtime-зависимость не вводятся. + +## Risks / Trade-offs + +- Краткий простой и единственный writer сохраняются; внешние процессы записи + должны быть исключены оператором при подключении существующей установки. +- Локальный backup не спасает от потери диска; свободное место и ручная очистка + остаются эксплуатационной обязанностью. Ошибка копирования блокирует миграции. +- Native hash может измениться между версиями Compose: сомнение приводит + к полному безопасному обновлению, а не к пропуску backup. +- Прерванная migration требует разбора, а не слепого повторения команды. + Ни служебный маркер, ни последний успешный тег не являются копией базы. +- Доверие к Git origin и неизменяемым Nexus release tags остаётся существующей + границей поставки; собственная система подписей здесь не добавляется. + +## Migration Plan + +Подготовить самодостаточный комплект и проверить безопасный lifecycle на +disposable данных. Затем обновить docs, сохранив development-инструкцию. +Для действующего сервера сохранить старые настройки и соответствие images, +подключить существующие абсолютные пути и project, выполнить первый полный +переход с backup. Старый способ запуска не выполнять параллельно. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | `required` | +| Trigger | Ошибка migration/readiness, прерывание либо выявленная регрессия | +| Actions | Остановить API при риске для данных; отдельно выбрать совместимый image либо explicit restore; незавершённый маркер снять только после проверки состояния | +| Data safety | Не восстанавливать БД автоматически; перед explicit restore сохранить текущий каталог, выбрать копию, подтвердить потерю новых записей и наличие прежнего ключа | +| Verification | На отдельной копии проверить целостность БД, совместимость схемы, auth/cards и расшифровку подключения; после восстановления повторить smoke через HTTPS | diff --git a/openspec/changes/versioned-deployment-bundle/proposal.md b/openspec/changes/versioned-deployment-bundle/proposal.md new file mode 100644 index 0000000..8e5ef9b --- /dev/null +++ b/openspec/changes/versioned-deployment-bundle/proposal.md @@ -0,0 +1,40 @@ +## Why + +После выпуска компонентов оператор вручную выбирает совместимые версии +и повторяет порядок обновления. Нужен один выбранный релиз всей установки +и одна команда, сохраняющая данные и настройки существующего сервера. + +## What Changes + +- Версионируемый комплект закрепляет проверенную пару версий интерфейса и API. +- Оператор явно выбирает релиз; публикация компонента не обновляет сервер. +- Обновление выполняется одной командой с предварительными проверками, + сохранением данных и диагностируемым результатом. +- Обновление только интерфейса не прерывает работу API и не затрагивает базу. +- Перед обновлением API создаётся согласованная резервная копия. Неудачное + обновление не запускает автоматическое восстановление или откат. +- Инструкция охватывает подготовку сервера, первую установку, подключение + существующей установки, выпуск комплекта, обновление и ручное восстановление. + +Non-goals: настройка серверной инфраструктуры командой развёртывания, +автоматическое обновление по выходу компонента, непрерывная доступность, +несколько экземпляров API, автоматическая очистка копий и внешнее backup-хранилище. + +## Capabilities + +### New Capabilities + +- `specs/deployment-bundle/spec.md`: выбор полного релиза, безопасная автоматизация + обновления и сохранение существующей установки. + +### Modified Capabilities + +Нет. Новый контракт дополняет контейнерную поставку; существующий active change +`containerized-application-delivery` и его capability `application-delivery` +не изменяются и не архивируются в рамках этой работы. + +## Impact + +Основной пользователь — оператор одной установки WealthLedger. Изменяются +комплект развёртывания, эксплуатационная документация и требования к обновлению. +Прикладные функции интерфейса и API остаются прежними. diff --git a/openspec/changes/versioned-deployment-bundle/specs/deployment-bundle/spec.md b/openspec/changes/versioned-deployment-bundle/specs/deployment-bundle/spec.md new file mode 100644 index 0000000..60e46c0 --- /dev/null +++ b/openspec/changes/versioned-deployment-bundle/specs/deployment-bundle/spec.md @@ -0,0 +1,169 @@ +## Purpose + +Определяет установку выбранного релиза WealthLedger одной командой, +сохранение состояния сервера и безопасный результат успешного или неудачного обновления. + +## ADDED Requirements + +### Requirement: Релиз определяет полную пару компонентов + +Комплект SHALL иметь отдельную неизменяемую версию, однозначно определяющую +проверенную пару версий интерфейса и API. Оператор MUST явно выбирать релиз +комплекта. Публикация компонента MUST NOT автоматически выпускать комплект +или обновлять сервер; локальные настройки MUST NOT незаметно подменять пару. + +#### Scenario: Установка выбранного релиза + +- **WHEN** оператор выбирает опубликованный релиз комплекта +- **THEN** устанавливается ровно закреплённая в нём пара, даже если опубликованы + более новые версии компонентов + +#### Scenario: Отсутствующий или изменённый релиз + +- **WHEN** выбранный релиз отсутствует либо ранее полученная версия теперь + указывает на другое содержимое +- **THEN** операция завершается ошибкой до изменения работающей установки + +### Requirement: Проверки предшествуют изменению установки + +Одна команда SHALL выполнять установку выбранного релиза на подготовленном +сервере. До остановки приложения она MUST проверить доступность комплекта, +его компонентов, обязательных настроек и постоянных ресурсов. +Отсутствующая база существующей установки MUST NOT трактоваться как новая +пустая установка. Секреты MUST NOT попадать в диагностический вывод. + +#### Scenario: Компонент недоступен + +- **WHEN** выбранный компонент нельзя получить или обязательный сертификат + либо каталог данных недоступен +- **THEN** обновление завершается с понятной ошибкой, не останавливая + работающие компоненты и не создавая замену отсутствующим данным + +#### Scenario: Новая установка + +- **WHEN** оператор явно подготовил новую установку с отдельным пустым + хранилищем и необходимыми настройками +- **THEN** команда подготавливает данные и запускает выбранный релиз, + не требуя предыдущей установленной версии + +### Requirement: Существующая установка сохраняет состояние + +Подключение существующей установки и последующие обновления MUST сохранять +её пользовательские данные, сессии, ключи шифрования и настройки доверия +внешнему провайдеру. Эти ресурсы MUST храниться независимо от обновляемого +комплекта. Команда MUST NOT запускать второй API на той же базе или генерировать +новые ключи вместо действующих. + +#### Scenario: Переход с прежнего способа запуска + +- **WHEN** оператор подключает существующую установку к комплекту +- **THEN** используются её прежние данные и ключи; после запуска доступны + существующие записи и расшифровка сохранённого подключения + +### Requirement: Обновление затрагивает только изменённые компоненты + +Если меняется только интерфейс, команда MUST сохранять работающий API, +не изменять данные и не запускать миграции или резервное копирование. +Изменение настроек API SHALL считаться изменением API, даже при той же версии. +Обновление только API MUST NOT пересоздавать неизменный интерфейс. +Повторное применение уже установленного исправного релиза с теми же настройками +MUST NOT выполнять ненужное обновление или резервное копирование. + +#### Scenario: Новый интерфейс при прежнем API + +- **WHEN** выбранный релиз меняет только интерфейс, а API и его настройки + подтверждённо совпадают с установленными +- **THEN** обновляется только интерфейс, API продолжает работу без миграций + и резервного копирования для этой операции + +#### Scenario: Та же версия API с другими настройками + +- **WHEN** версия API прежняя, но его настройки изменились +- **THEN** применяется безопасная процедура обновления API + +#### Scenario: Повтор успешно установленного релиза + +- **WHEN** выбран уже установленный исправный релиз без изменений настроек +- **THEN** команда сообщает, что обновление не требуется, без остановки + компонентов и без новой резервной копии + +### Requirement: Обновление API сохраняет согласованную копию + +Перед обновлением API существующей установки команда MUST остановить запись +и успешно сохранить согласованную копию всех постоянных данных до миграций. +Копии MUST находиться вне обновляемого комплекта; команда MUST NOT удалять +или перезаписывать прежние копии автоматически. Неуспешная копия MUST +блокировать миграции. Новая пустая установка не требует фиктивного backup. + +#### Scenario: Подготовка обновления API + +- **WHEN** выбран релиз с изменением API существующей установки +- **THEN** до миграций сохраняется отдельная согласованная копия при отсутствии + пишущих процессов, и оператор получает её расположение + +#### Scenario: Копию сохранить не удалось + +- **WHEN** резервное копирование завершилось ошибкой +- **THEN** миграции и запуск новой версии не выполняются, существующие данные + и прежние копии сохраняются, операция сообщает ошибку + +### Requirement: Неудачное обновление не скрывает риск для данных + +Ошибка миграции MUST блокировать запуск новой версии API. Если после миграции +API не готов, команда MUST остановить неисправный API и завершиться с ошибкой. +Любая неуспешная проверка обновляемого компонента MUST блокировать отметку +релиза как успешно установленного. Диагностика SHALL указывать этап сбоя, +предыдущую версию и доступный backup без раскрытия секретов. +Автоматический откат или восстановление данных MUST NOT выполняться. + +#### Scenario: Миграция не прошла + +- **WHEN** подготовка схемы завершается ошибкой +- **THEN** API остаётся остановленным, а результат содержит сведения для + отдельного решения о восстановлении + +#### Scenario: Новый API не готов + +- **WHEN** миграция завершилась успешно, но проверка готовности API не прошла +- **THEN** неисправный API останавливается без автоматического запуска старой + версии или восстановления базы + +#### Scenario: Неудачное обновление интерфейса + +- **WHEN** при обновлении только интерфейса его проверка запуска не прошла +- **THEN** операция завершается ошибкой, API продолжает работу, база не + восстанавливается, релиз не отмечается успешно установленным + +### Requirement: Повтор и конкуренция не нарушают порядок обновления + +Команда MUST исключать одновременные изменения одной установки несколькими +запусками. Прерванное или неуспешное обновление MUST отличаться от успешно +установленного релиза. Повтор команды после потенциального изменения данных +MUST NOT скрывать незавершённую операцию или автоматически восстанавливать данные. + +#### Scenario: Второе обновление + +- **WHEN** для установки уже выполняется обновление +- **THEN** второй запуск завершается без изменения её состояния + +#### Scenario: Процесс прерван после начала обновления + +- **WHEN** предыдущая операция прервана после остановки API или начала миграции +- **THEN** следующий запуск сообщает о незавершённой операции и необходимости + проверки состояния до нового обновления + +### Requirement: Оператор располагает полным порядком эксплуатации + +Инструкция комплекта SHALL описывать последовательность подготовки сервера, +первой установки и подключения существующей установки, подготовку проверенной +пары и выпуск релиза, обновление, диагностику, откат версии и отдельное +восстановление базы. Она MUST указывать сохранение ключей, ручную очистку +локальных копий и отсутствие защиты от потери диска только локальным backup. +Восстановление MUST требовать явного выбора копии и принятия потери более +новых данных; откат API — проверки совместимости с текущей схемой. + +#### Scenario: Оператор восстанавливает установку + +- **WHEN** оператор использует инструкцию после неудачного обновления +- **THEN** он может отдельно выбрать совместимую версию приложения или + восстановление конкретной копии с явным учётом потери новых данных diff --git a/openspec/changes/versioned-deployment-bundle/tasks.md b/openspec/changes/versioned-deployment-bundle/tasks.md new file mode 100644 index 0000000..9160ba4 --- /dev/null +++ b/openspec/changes/versioned-deployment-bundle/tasks.md @@ -0,0 +1,68 @@ +## 1. Самодостаточный комплект + +- [x] 1.1 [deploy] Создать production Compose, release.env и пример server env, переиспользовав существующие images/migration/healthchecks и сохранив read-only CA mount и постоянные абсолютные пути. + - **Covers:** `deployment-bundle` — `Релиз определяет полную пару компонентов`; `deployment-bundle` — `Существующая установка сохраняет состояние` + - **Verification:** `docker compose --env-file --env-file release.env -f compose.yaml config --quiet`; inspect подтверждает выбранную пару, один backend, общий data mount migration/backend, отсутствие host port API и read-only сертификат; exported image variables не подменяют релиз через entrypoint. + +- [x] 1.2 [deploy] Реализовать получение выбранного тега, выполнение snapshot его commit, preflight, flock и минимальные записи состояния в одном deploy.sh согласно Solution Assessment. + - **Covers:** `deployment-bundle` — `Релиз определяет полную пару компонентов`; `deployment-bundle` — `Проверки предшествуют изменению установки`; `deployment-bundle` — `Повтор и конкуренция не нарушают порядок обновления` + - **Verification:** `bash -n deploy.sh`; сценарная проверка отсутствующего/переназначенного тега, недоступного image/CA/data, отказа второго запуска и отсутствия изменений runtime при preflight failure; snapshot использует deploy-логику выбранного commit. + +## 2. Безопасный lifecycle и его доказательство + +- [x] 2.1 [deploy] Реализовать first install/adoption, выбор изменённых сервисов, stop/backup/migration/readiness и остановку последовательности при ошибке без автоматического rollback. + - **Covers:** `deployment-bundle` — `Проверки предшествуют изменению установки`; `deployment-bundle` — `Существующая установка сохраняет состояние`; `deployment-bundle` — `Обновление затрагивает только изменённые компоненты`; `deployment-bundle` — `Обновление API сохраняет согласованную копию`; `deployment-bundle` — `Неудачное обновление не скрывает риск для данных`; `deployment-bundle` — `Повтор и конкуренция не нарушают порядок обновления` + - **Verification:** На disposable Compose project проверить явный `--init`, adoption существующей тестовой БД, обновление обоих компонентов и API отдельно, неизменность container ID API при frontend-only update, отсутствие повторного backup при healthy no-op; изменение backend config включает backup даже с прежним image. + +- [x] 2.2 [deploy] Добавить одну исполняемую проверку tests/deploy.sh без нового test framework и закрепить локальный gate для shell и сценариев обновления. + - **Covers:** `deployment-bundle` — `Релиз определяет полную пару компонентов`; `deployment-bundle` — `Проверки предшествуют изменению установки`; `deployment-bundle` — `Существующая установка сохраняет состояние`; `deployment-bundle` — `Обновление затрагивает только изменённые компоненты`; `deployment-bundle` — `Обновление API сохраняет согласованную копию`; `deployment-bundle` — `Неудачное обновление не скрывает риск для данных`; `deployment-bundle` — `Повтор и конкуренция не нарушают порядок обновления` + - **Verification:** `bash -n deploy.sh tests/deploy.sh`, `bash tests/deploy.sh`, `git diff --check`; fault injection доказывает отсутствие migration после failed copy, отсутствие backend start после failed migration, stop после backend readiness failure, сохранение backend после frontend failure, неизменность данных/старых backups, блокировку конкуренции и незавершённой операции. Проверка использует временные данные; production paths недопустимы. + +## 3. Эксплуатация и документация + +- [x] 3.1 [deploy] Описать в README подготовку сервера, сохранение старых ключей и Compose project, install/update, выпуск тега с проверенной парой, диагностику, ручной rollback/restore и очистку backups; закрепить реально опубликованную первую пару перед выпуском. + - **Covers:** `deployment-bundle` — `Оператор располагает полным порядком эксплуатации`; `deployment-bundle` — `Релиз определяет полную пару компонентов` + - **Verification:** Пройти инструкцию на изолированной установке с тестовыми данными; проверить login/refresh через HTTPS, сохранение записей и расшифровку тестового подключения прежним ключом, `PRAGMA integrity_check` на копии и explicit restore; сравнить actual images с release.env. Первый тег не публиковать до проверки пары. + +- [x] 3.2 [docs] Завершить ADR 016 и glossary, обновить production-раздел контейнерной поставки под deploy с сохранением development-инструкции и границ ручного recovery. + - **Covers:** `N/A` — docs объясняет поставленный механизм из Design и направляет оператора к единственному README комплекта; самостоятельного runtime behavior не добавляет. + - **Verification:** После `nvm use --silent` выполнить `npm run verify` в docs; проверить маршрут README → deployment → ADR и отсутствие обещаний ещё не реализованных операций или дублирующего production runbook. + +- [x] 3.3 [root] Направить production-раздел root README к комплекту deploy, сохранив существующие development-команды. + - **Covers:** `N/A` — workspace-навигация должна вести к владельцу production lifecycle из Design; поведение приложения и root tooling не меняются. + - **Verification:** `git diff --check` в root; просмотр маршрута root README → deploy README и docs/architecture/deployment.md, проверка неизменности development-команд. + +## 4. Сверка поставки + +- [x] 4.1 [specs] Сверить артефакты с фактической реализацией, сохранить coverage всех требований и проверить canonical change без изменения чужого containerized-application-delivery. + - **Covers:** `N/A` — requirements и checklist должны оставаться контрактом реализованного комплекта; задача поддерживает точность SDD, а не вводит новое поведение приложения. + - **Verification:** После `nvm use --silent`: `openspec schema validate wealth-ledger-sdd --verbose`; `openspec validate versioned-deployment-bundle --type change --strict --store wealth-ledger-specs`; `openspec validate --specs --strict --store wealth-ledger-specs`; `npm run verify`; вручную проверить task coverage и соответствие дополнительному owner deploy из Delivery Map. + +- [x] 4.2 [workspace] Выполнить итоговую сверку сценариев установки, ошибок и восстановления с готовыми образами и результатами локальных gates затронутых репозиториев. + - **Covers:** `N/A` — объединяет доказательства deploy/specs/docs для согласованной пары, не владеет реализацией какого-либо репозитория и не заменяет independent review. + - **Verification:** На disposable данных сверить успешный update, frontend-only, interrupted update и recovery с Delta Specs; зафиксировать фактические версии и результаты gates. Проверки реального сервера не подменять локальным smoke; production rollout не входит в эту задачу. + +## 5. Implementation checkpoint + +2026-09-20: implementation-owned задачи выполнены; готово к независимой Validation. +Change остаётся active. Первый тег deploy не опубликован, рабочий сервер не изменялся. + +- Deploy: `bash -n deploy.sh tests/deploy.sh`, `bash tests/deploy.sh` — PASS. + Проверены selective update/no-op, config drift, сохранение ключей, symlink guard, + ошибки pull/preflight/copy/migration/backend/frontend, concurrent lock и pending. +- `DOCKER_DEFAULT_PLATFORM=linux/amd64 bash tests/deploy.sh --docker-restore` — PASS: + заменённый bind-каталог читается после force-recreate. Без пересоздания исходный + live repro читал старые данные; README recovery исправлен по этому результату. +- Disposable smoke с реальными Nexus images `0.2.3` — PASS: init, no-op по native + Compose hash, frontend-only/backend-only config update, stopped backup, + `PRAGMA integrity_check`, HTTPS deep link, registration, Secure-cookie и refresh. +- Explicit restore на синтетических данных — PASS: login, карта и её баланс, + отсутствие более новой карты и расшифровка тестового токена штатным TbankCrypto. + Оба тестовых Compose projects удалены; настоящие пользовательские данные + и реальное подключение к T-Bank не использовались. +- OCI revisions совпали с Git tags компонентов: frontend + `f486ae4fb2d06e71779901bde283950542f27ed2`, backend + `0862d56c4e4d9dd554556c3992cc073193020ff1`. +- Среда интеграционной проверки: Docker Desktop, Linux amd64 images через + эмуляцию на arm64 Mac, Compose 5.5.1; host flock в smoke заменён stdlib fcntl. + Это не проверка конфигурации целевого Linux-сервера или его registry login.