# Контейнерная разработка и поставка WealthLedger Основание — утверждённые [Proposal](proposal.md), [Delta Specs](specs/application-delivery/spec.md) и [Design revision 1](design.md). UI/UX package неприменим: пользовательские экраны и interactions не меняются. Checkbox закрывается только после выполнения действия и его Verification. Перед Node-командами активируется `.nvmrc` соответствующего независимого репозитория. Git-команды выполняются из репозитория-владельца; production data, secrets и опубликованные release tags не используются как test fixtures. Задачи выполняются по порядку разделов. Frontend 2.1–2.2 и backend 3.1–3.2 могут идти параллельно после 1.2. Workspace orchestration начинается после готовности обоих component images. Реальный server rollout выполняется только после локального production smoke и документированного backup. ## 1. SDD и внешние prerequisites - [x] 1.1 [specs] Проверить утверждённый SDD package закреплённым OpenSpec CLI и сохранить canonical change структурно валидным до Apply. - **Covers:** `N/A` — обязательный gate canonical SDD authority; он не добавляет runtime behavior, но разрешает реализацию утверждённых требований и Design. - **Verification:** с версией из `openspec/.cli-version` выполнить `openspec schema validate wealth-ledger-sdd --verbose`, `openspec validate containerized-application-delivery --type change --strict --store wealth-ledger-specs`, `openspec validate --specs --strict --store wealth-ledger-specs`, затем `npm run verify`. - **Result (2026-09-16):** по явному уточнению владельца использован установленный OpenSpec 1.13.0 вместо 1.10.0. Все четыре проверки прошли; 25 specs passed, 0 failed. Repository-wide pin не изменён. - [ ] 1.2 [workspace] Подтвердить prerequisites выбранного Solution Assessment: Docker Compose на development/server hosts, Docker-capable Gitea runner, Container Registry и раздельные package write/read credentials. - **Covers:** `N/A` — техническое предусловие утверждённой Compose/Registry delivery; оно не вводит самостоятельное поведение приложения и блокирует custom fallback при отсутствии platform capability. - **Verification:** зафиксировать поддерживаемые Docker/Compose и Gitea versions; `docker version`, `docker compose version`, успешные authenticated registry login для CI publisher и server pull-only identity без вывода credentials. - **Partial result (2026-09-16):** локально Docker Desktop 4.91.0, Engine 29.8.0 (Linux arm64), Compose 5.5.1; Gitea API сообщает 1.27.3. Server Docker/Compose и Docker-capable runner пока не подтверждены. MCP identity не имеет доступа к Actions secrets (`user should be the owner of the repo`); раздельные publisher/pull-only login не проверены. Publication и server rollout остаются внешними gates; независимая локальная реализация продолжается. ## 2. Frontend image и ingress - [x] 2.1 [frontend] Добавить development и production image targets: Dockerized Vite watch с same-origin `/api/` proxy и nginx runtime со SPA fallback, API proxy, runtime DNS re-resolution, cache policy и non-root-compatible filesystem. - **Covers:** `application-delivery` — `Локальная среда воспроизводит границы поставленного приложения`; `application-delivery` — `Серверное приложение имеет одну внешнюю точку входа`. - **Verification:** собрать оба targets; development container через container network отдаёт HMR и сохраняет `/api/v1/...`; production container отдаёт SPA/deep link, проксирует API без rewrite, не раскрывает backend port и после backend recreation подключается к новому container address. - [ ] 2.2 [frontend] Добавить release-tag workflow публикации frontend image с проверкой версии, canonical frontend gate, OCI revision metadata и immutable semver tags без `latest`. - **Covers:** `application-delivery` — `Поставка допускает независимое обновление компонентов`. - **Verification:** workflow syntax принимается Gitea; controlled candidate run после `npm run verify` публикует image, registry возвращает digest, clean pull по digest запускает тот же static artifact; credential отсутствует в logs и image history. - [x] 2.3 [frontend] Выполнить frontend repository completion gate после packaging и routing changes. - **Covers:** `N/A` — repository-owned проверка реализации Design и отсутствие regression в существующем frontend; отдельного observable requirement не создаёт. - **Verification:** `npm ci`, `npm run verify`, `git diff --check`; вручную просмотреть новые Docker/nginx/workflow files и отсутствие secrets. ### Результаты frontend (2026-09-16) - 2.1: оба targets собраны на Linux arm64; Vite HMR WebSocket connected, `/api/v1/probe?check=path` сохранён. nginx read-only + tmpfs отдаёт deep link с `no-cache`, proxy сохраняет path/query/host/proto/request ID. Backend без host port пересоздан с другим IP (.2 → .5); nginx восстановил proxy без restart. - 2.2: release-tag workflow реализован с `ubuntu-latest` (label подтверждён владельцем). Реальный runner Docker access, registry write/read credentials, Gitea workflow acceptance и controlled publication пока не проверены. - 2.3: `npm ci`, `npm run verify`, `git diff --check` прошли. Docker context ограничен allowlist; `.env`, credentials и host dependencies не копируются. Новые Docker/nginx/workflow files просмотрены; publication secrets идут только в отдельный Docker login, не в image build arguments. ## 3. Backend image, migration и readiness - [x] 3.1 [backend] Добавить development и production image targets с Linux native dependencies, compiled application, generated Prisma client, runtime Prisma CLI/migrations, T-Bank proto assets, non-root process и выделенным data path. - **Covers:** `application-delivery` — `Локальная среда воспроизводит границы поставленного приложения`; `application-delivery` — `Постоянное состояние и секреты переживают штатный lifecycle`; `application-delivery` — `Обновление API сохраняет целостность данных`. - **Verification:** development target выполняет Nest watch; production target стартует из чистого image, находит proto contracts, принимает config только из environment, пишет только в data path; одноразовый `prisma migrate deploy` проходит на fresh и representative existing SQLite, а приложение не стартует после контролируемой migration failure. - [ ] 3.2 [backend] Добавить release-tag workflow публикации backend image с проверкой версии, полными backend gates, OCI revision metadata и immutable semver tags без `latest`. - **Covers:** `application-delivery` — `Поставка допускает независимое обновление компонентов`. - **Verification:** workflow syntax принимается Gitea; controlled candidate run выполняет `npm run verify` и `npm run test:e2e`, публикует image и получает digest; clean pull по digest выполняет migration и production start, credential отсутствует в logs и image history. - [x] 3.3 [backend] Выполнить backend repository completion gates и проверить container readiness без нового публичного endpoint. - **Covers:** `N/A` — repository-owned проверка packaging, existing auth boundary и SQLite integration из Design; самостоятельного нового API behavior нет. - **Verification:** `npm ci`, `npm run verify`, `npm run test:e2e`, `git diff --check`; healthcheck принимает завершённый HTTP-ответ existing auth route, а stopped/uninitialized process остаётся unhealthy. ## 4. Workspace Compose orchestration - [x] 4.1 [workspace] Добавить production Compose topology с version-tagged frontend/backend references, migration service, healthchecks, persistent host data directory, internal-only backend и единственным configurable frontend loopback port. - **Covers:** `application-delivery` — `Серверное приложение имеет одну внешнюю точку входа`; `application-delivery` — `Поставка допускает независимое обновление компонентов`; `application-delivery` — `Постоянное состояние и секреты переживают штатный lifecycle`; `application-delivery` — `Обновление API сохраняет целостность данных`. - **Verification:** `docker compose config` с non-secret fixture environment; rendered config содержит один host port, один backend replica, одинаковый backend image reference у migration/runtime и отдельный data mount; missing required image/config/secret values дают blocking configuration/start failure. - [x] 4.2 [workspace] Добавить development Compose override с local image targets, source watch/mounts, container-owned dependencies, отдельным local data volume и одним frontend browser origin. - **Covers:** `application-delivery` — `Локальная среда воспроизводит границы поставленного приложения`; `application-delivery` — `Постоянное состояние и секреты переживают штатный lifecycle`. - **Verification:** с чистыми local volumes запустить Compose; frontend edit появляется через HMR, backend edit перезапускает Nest, `/api/v1/...` идёт через frontend origin, host `node_modules` не используется, restart сохраняет local data, production data path отсутствует в rendered config. - [x] 4.3 [workspace] Обновить root tracking allowlist и README entrypoints без custom scripts или нового root CI, затем проверить orchestration configuration. - **Covers:** `N/A` — root только связывает независимые component repositories и объясняет стандартные Compose entrypoints согласно Design; runtime behavior реализуется component images и Compose tasks 4.1–4.2. - **Verification:** `docker compose config` для production и development variants, `git diff --check`, просмотр `git status --short` и staged paths подтверждают только явно разрешённые root files; root workflow остаётся только commitlint. ## 5. Эксплуатационная документация - [x] 5.1 [docs] Добавить ADR об ограниченном single-server SQLite deployment и обновить container architecture фактической Docker/Caddy topology, secret и persistence boundaries. - **Covers:** `application-delivery` — `Серверное приложение имеет одну внешнюю точку входа`; `application-delivery` — `Постоянное состояние и секреты переживают штатный lifecycle`. - **Verification:** ADR явно связывает решение с прежним local-only baseline, одним backend instance и triggers пересмотра; architecture diagram показывает только frontend port наружу, internal backend и persistent SQLite; ссылки и термины согласованы. - [x] 5.2 [docs] Описать development, first deployment, independent component update, stopped-backend backup, compatibility check, rollback и explicit restore runbooks. - **Covers:** `application-delivery` — `Локальная среда воспроизводит границы поставленного приложения`; `application-delivery` — `Поставка допускает независимое обновление компонентов`; `application-delivery` — `Обновление API сохраняет целостность данных`; `application-delivery` — `Восстановление не выдаёт откат приложения за откат данных`. - **Verification:** dry-run runbook на disposable data проходит без неописанного шага; frontend-only и backend-only paths не пересоздают другой component; restore требует отдельного решения и сообщает о потере данных после backup. - [ ] 5.3 [docs] Выполнить docs repository completion gate. - **Covers:** `N/A` — repository-owned проверка durable as-built documentation и ссылок без нового runtime behavior. - **Verification:** `npm ci`, `npm run verify`, `git diff --check`; проверить Mermaid rendering, взаимные ADR/architecture/runbook links и отсутствие credentials или host-private values. ## 6. Сквозная проверка поставки - [x] 6.1 [workspace] Проверить development Compose на чистом checkout и production Compose на локально собранных release-equivalent images. - **Covers:** `application-delivery` — `Локальная среда воспроизводит границы поставленного приложения`; `application-delivery` — `Серверное приложение имеет одну внешнюю точку входа`. - **Verification:** development подтверждает HMR/watch и same-origin login; production smoke подтверждает SPA deep link, registration/login/refresh, API proxy, отсутствие host backend port и clean startup через migration service. - [x] 6.2 [workspace] На disposable representative SQLite проверить persistence, frontend-only update, backend-only update, migration failure и rollback boundaries. - **Covers:** `application-delivery` — `Поставка допускает независимое обновление компонентов`; `application-delivery` — `Постоянное состояние и секреты переживают штатный lifecycle`; `application-delivery` — `Обновление API сохраняет целостность данных`; `application-delivery` — `Восстановление не выдаёт откат приложения за откат данных`. - **Verification:** restart сохраняет user/session/card data и decryptable test connection; frontend update не recreates backend/data, backend update не recreates frontend; failed migration не делает backend healthy; возврат совместимого image сохраняет current data, а restore отдельной backup copy выполняется только в изолированном scenario с явной проверкой потерянного интервала. - [ ] 6.3 [workspace] Проверить release images из Gitea по выбранным version tags перед первым server rollout и сохранить точную установленную пару версий. - **Covers:** `application-delivery` — `Поставка допускает независимое обновление компонентов`; `application-delivery` — `Постоянное состояние и секреты переживают штатный lifecycle`. - **Verification:** pull-only server identity получает оба versioned images; `docker image inspect` подтверждает expected revisions; production smoke проходит на этих images с disposable config/data, после чего оператор записывает выбранные image references без secrets. ## Apply verification — 2026-09-16 ### Backend (3.1–3.3) - Development и production targets собраны на Linux arm64 / Node 24.21.0. Node/nginx base images закреплены digest; OS packages — точными версиями. Чистый production runtime обнаружил неверный прежний fallback proto path: Nest копирует assets в `dist/src`; loader теперь использует `__dirname`. Nest watch использует `ps`; `procps` добавлен только в development target. - `npm ci`, `npm run verify` — PASS (35 suites). `npm audit` при переносе существующего Prisma 7.10.0 в runtime dependencies — 0 vulnerabilities. - `npm run test:e2e` на macOS дважды завершился native Node SIGABRT (`RemoveEnvironmentCleanupHook`, `env != nullptr`). Тот же штатный gate выполнен в Linux development image: **8 suites / 98 tests PASS**. Это Linux delivery proof; macOS native cleanup limitation не скрывается. - `npm run test:container -- wealthledger-backend:production` — runnable image regression: fresh/existing migration, runtime без source/.env/compiler, non-root + read-only filesystem, существующий auth readiness, user/session persistence. Он же вызывается publish workflow после clean pull. - Controlled broken SQLite migration возвращает non-zero; backend остаётся остановленным. После возвращения исправного data path migration идемпотентна. - 3.2 остаётся открытой: YAML и shell syntax проверены локально, но Gitea acceptance, authenticated publication и pull по registry digest не выполнены. ### Compose, docs и lifecycle (4–6) - Production/development `docker compose config` — PASS. Проверены один loopback host port, backend scale 1, общий backend image/data mount у migration/runtime, readiness dependency и отсутствие production bind mount в development services. Пустая production environment блокирует config. - `docker compose up -d --wait` — PASS для development и production на локально собранных release-equivalent images. Production smoke использовал временный override local image references; canonical config остаётся digest-pinned. Реальный Gitea digest pull этим не подменяется. - Development: source edit вызвал HMR event и изменённый HTTP module, backend source edit — новый Nest process и готовый API. Временные изменения восстановлены. Same-origin registration/login/refresh и data restart PASS. - Clean checkout proof: отдельный HEAD export + task diff, без host .env и node_modules, новый Compose project/volume; startup и HMR/watch PASS. - Browser proof (bundled Playwright/Chrome): clean development и production registration/login/reload-refresh PASS; все API requests same-origin, httpOnly/path сохранены, production cookie Secure. Caddy/TLS вне локальной проверки. - Production: SPA deep link, registration/login/refresh, неизменённый API path, отсутствие backend host port, non-root read-only runtime — PASS. - Representative disposable SQLite содержит user/session/card и connection, зашифрованный настоящим TbankCrypto на вымышленном token. Restart, migrations и independent recreation сохраняют данные и расшифровку без внешнего T-Bank. - Отдельные local fixture image IDs проверили frontend-only/backend-only update без recreation другого компонента. Возврат прежнего совместимого backend image сохраняет запись после backup. Это compatibility fixture, не обещание совместимости произвольных будущих релизов. - Stopped-backend backup и isolated restore с отдельным project/port/data path — PASS: login, карта до backup и decryptable connection доступны; запись после backup отсутствует в восстановленной копии. Текущая база не заменялась. - Docs: ADR 015 расширяет ADR 001; topology и runbook взаимно связаны. `npm ci`, `npm run verify` — PASS (44 Markdown files), `git diff --check` — PASS. Mermaid source семантически просмотрен, но renderer в доступном окружении не найден; **5.3 оставлена открытой до проверки rendering**. - Root Boy Scout/allowlist review: только Compose, два безопасных env examples, README и .gitignore; новых root scripts/dependencies/CI нет. Source markers watch checks восстановлены. Чужие runtime data и checkout changes сохранены. - Docker disk был заполнен. После адресной очистки intermediates этой задачи и отдельно разрешённой очистки unused build cache старше суток проверки продолжены; чужие images/containers/volumes не удалялись. ### Оставшиеся внешние gates - 1.2: server Docker/Compose, Docker capability runner `ubuntu-latest`, раздельные package writer и server pull-only authenticated login. - 2.2/3.2: реальные Gitea candidate workflows и публикация frontend/backend, OCI revisions, registry digests, clean pull и отсутствие credentials в logs. Нужны Actions secrets `REGISTRY_USERNAME`/`REGISTRY_TOKEN` в каждом component repo и отдельная server identity. Workflow не публикует `latest` и fail-closed отказывается от overwrite или недоказанного отсутствия tag. - 6.3: smoke опубликованной пары version tags на disposable server config/data, запись installed image references/revisions до первого server rollout. Server rollout, Caddy, TLS и реальный финансовый запрос не выполнялись. ### Упрощение deployment configuration по запросу владельца Обязательные `FRONTEND_DIGEST`/`BACKEND_DIGEST` и отдельные repository fields заменены на `FRONTEND_IMAGE`/`BACKEND_IMAGE` с release version tags. Design и runbook синхронизированы. Результаты выше описывают первоначальную проверку; обязательный digest pinning более не является deployment contract. Migration и backend по-прежнему используют один image reference. Verification упрощения: production/development `docker compose config` PASS; разные version tags выбираются независимо, migration/runtime получают один backend image, один loopback port и development volume сохранены. Пустые image/secret values блокируют config. Docs `npm run verify`, OpenSpec strict change validation и `git diff --check` в affected repos — PASS. По следующему запросу владельца из `FROM` удалены base image digests: используются `node:24.21.0-bookworm-slim` и `nginx:1.28.0-alpine`. Version tags не гарантируют неизменность содержимого базового image. Verification: повторная сборка production/development targets обоих компонентов — PASS (с использованием Docker cache). Dockerfile остаются в component repos, чтобы standalone build и release CI не зависели от root checkout. По запросу владельца удалены дополнительные commit tags из обоих workflows; release tag защищён от перезаписи, revision остаётся в OCI label. Production Compose наследует healthchecks из images; development содержит собственные checks с прежними таймингами, migration отключает check. Verification: production/development Compose config и invariants, healthchecks локальных production images, shell syntax обоих workflows, docs verify, OpenSpec strict validation и diff checks — PASS. Registry publication остаётся внешней непроверенной частью задач 2.2/3.2.