spec: define containerized application delivery #10
Specs security / security (pull_request) Successful in 6s
Specs verify / verify (pull_request) Successful in 8s
Conventional commits / commits (pull_request) Successful in 11s
Specs security / security (push) Successful in 6s
Specs verify / verify (push) Successful in 9s

This commit was merged in pull request #11.
This commit is contained in:
2026-09-16 09:41:59 +03:00
parent e4f10dedef
commit b34f23a230
5 changed files with 619 additions and 0 deletions
@@ -0,0 +1,219 @@
# Контейнерная разработка и поставка 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.