From 97069d6f6d0719d6fcee30b1ce8adbda41bd3a6d Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Tue, 15 Sep 2026 20:45:40 +0300 Subject: [PATCH 1/3] docs: make documentation checks self-contained --- .ci/refs.json | 6 --- .gitea/workflows/verify.yml | 32 ++------------- AGENTS.md | 10 +++-- CONTRIBUTING.md | 28 ++++++------- README.md | 40 ++++++++++++++++++- architecture/cross-cutting/data-freshness.md | 8 ++-- architecture/cross-cutting/ui-resilience.md | 6 +-- architecture/http-api.md | 13 +++++- design/overview.md | 2 +- domains/accounts/overview.md | 2 +- domains/authentication/overview.md | 4 +- domains/cards/overview.md | 2 +- domains/income/overview.md | 4 +- domains/income/upcoming-events.md | 4 +- domains/instruments/bonds.md | 12 +++--- domains/instruments/stocks.md | 10 ++--- domains/operations/overview.md | 2 +- domains/portfolio/all-accounts.md | 10 ++--- domains/portfolio/data-reference.md | 4 +- domains/portfolio/flows/portfolio-loading.md | 10 ++--- .../how-to/investigate-discrepancy.md | 10 ++--- domains/portfolio/overview.md | 4 +- integrations/moex.md | 4 +- integrations/nkr.md | 2 +- integrations/tbank.md | 8 ++-- 25 files changed, 125 insertions(+), 112 deletions(-) delete mode 100644 .ci/refs.json diff --git a/.ci/refs.json b/.ci/refs.json deleted file mode 100644 index c2b0c57..0000000 --- a/.ci/refs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "main": "2567a755569e6ef6ba12063b62f202fe52884ce7", - "frontend": "0244acf429c525b23117f165bbe52ef5403eefd3", - "backend": "660b250ecd05e38784690b9a9adb381764c0ca7c", - "specs": "9e227a563dda67620f0ad78228aa8a4c18bf35a0" -} diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml index c903f95..9981701 100644 --- a/.gitea/workflows/verify.yml +++ b/.gitea/workflows/verify.yml @@ -8,39 +8,13 @@ on: jobs: verify: runs-on: ubuntu-latest - defaults: - run: - working-directory: workspace/docs steps: - uses: https://github.com/actions/checkout@v4 - with: - path: workspace/docs - - name: Checkout compatible peer revisions - env: - SOURCE_BRANCH: ${{ github.head_ref || github.ref_name }} - TARGET_BRANCH: ${{ github.base_ref || github.ref_name }} - run: | - set -eu - for repo in main frontend backend specs; do - ref=develop - case "$SOURCE_BRANCH:$TARGET_BRANCH" in - release/*:*|*:main) - ref=$(jq -er --arg repo "$repo" '.[$repo]' .ci/refs.json) - printf '%s\n' "$ref" | grep -Eq '^[0-9a-f]{40}$' - ;; - esac - target="../$repo" - if [ "$repo" = main ]; then target=..; fi - git init "$target" - git -C "$target" remote add origin "https://git.personal.ksv741.ru/WealthLedger/$repo.git" - git -C "$target" fetch --depth 1 origin "$ref" - git -C "$target" checkout --detach FETCH_HEAD - git -C "$target" rev-parse HEAD - done - git rev-parse HEAD + - name: Record checked revision + run: git rev-parse HEAD - uses: https://github.com/actions/setup-node@v4 with: - node-version-file: workspace/docs/.nvmrc + node-version-file: .nvmrc - run: npm ci - name: Check Markdown and relative links run: npm run verify diff --git a/AGENTS.md b/AGENTS.md index 27885e3..d16b722 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,10 @@ затем только затронутую тему. Docs объясняет систему; требования принадлежат основным спецификациям, реализация и coding conventions — соседним репозиториям. -Проверь существенные факты адресно по спецификациям, ADR и реализации с учётом -веток и незакоммиченных изменений. Расхождение обозначь, не выбирая источник +Проверь существенные факты по локальным тематическим страницам и ADR; +внешние источники используй как справочные свидетельства, когда они доступны. +Недостающие факты отмечай явно; инструкции и проверки не требуют других checkout. +Расхождение обозначь, не выбирая источник победителем автоматически. Перенос сохраняет полезное содержание и входящие ссылки. Docs описывает текущее устройство системы; история поставок остаётся в Git. Отчёты аудитов и исследований в docs не размещаются. Подтверждённые решения @@ -19,6 +21,6 @@ ## Ветки и релизы Работа — в отдельной ветке от `develop`, затем PR в `develop`; в `main` напрямую -не пушить. Release/hotfix и синхронизация веток — по [root README](../README.md#релизы-и-ветки). +не пушить. Release/hotfix и синхронизация веток — по [README](README.md#релизы). Commits и заголовки PR — Conventional Commits; для спецификаций допустим `spec:`. -Перед подготовкой релиза прочитай root README; версии вручную не повышать. +Перед подготовкой релиза прочитай местный README; версии вручную не повышать. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c5e1ab..d1669fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,13 +8,13 @@ Docs даёт самостоятельное понимание продукта | Вид знания | Где поддерживается | | --- | --- | -| Согласованное проверяемое поведение: требования и сценарии | [Основные спецификации](../specs/AI_INDEX.md) в `specs/openspec/specs/` | +| Согласованное проверяемое поведение: требования и сценарии | [Основные спецификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AI_INDEX.md) в `specs/openspec/specs/` | | Предложения изменений, их проектирование и задачи | OpenSpec changes в существующем Store `wealth-ledger-specs`, `specs/openspec/changes/` | | Понимание системы, областей и взаимодействий | Тематические страницы docs | | Принятые решения и подтверждённые причины | Единственный журнал [docs/adr](adr/README.md) | | Конкретная реализация и технические контракты | Код, тесты, схемы, конфигурация; публичный OpenAPI формирует backend | | Размещение кода, conventions, команды разработки и проверки | Локальные инструкции frontend/backend | -| Выбор репозитория и типа знания | Корневой [AGENTS](../AGENTS.md) | +| Работа над docs | Локальные [AGENTS](AGENTS.md) и этот документ | История аудитов и проверок остаётся в Git, Issues и PR. `CHANGELOG.md` генерируется release-it из commits; вручную дублировать его на тематических страницах не нужно. @@ -86,9 +86,11 @@ Docs объясняет смысл контракта, не вводя неза Нужные точные значения сопровождай определяющим источником, чтобы не создать конкурирующую норму. Паспорта страниц, Evidence-секции и номера строк не требуются. -Внутри docs используем относительные Markdown-ссылки. Между репозиториями — -относительный путь через соседний корень, например `../specs/AI_INDEX.md` из README. -Проверяй регистр, файл и якорь в фактическом workspace. Перенос требует исправить +Внутри docs используем относительные Markdown-ссылки. Внешние репозитории — +необязательные справочные веб-ссылки на опубликованный источник. Основной текст +и инструкции должны быть понятны без открытия этих ссылок. Проверяй локальные +файлы/якоря; для удалённых источников не обещай проверку их доступности CI. +Перенос требует исправить входящие ссылки активных Markdown-документов; исторические архивы и dated audits сохраняют адреса своего времени. Короткий указатель оставляем только при реальной необходимости совместимости, без второй копии содержания. @@ -153,8 +155,7 @@ npm run verify Существующий gate запускает Markdown lint с проверкой относительных ссылок и якорей, затем `git diff --check`. Он охватывает новые Markdown-файлы; обычный diff их не показывает, поэтому отдельно просмотри untracked-содержание. -При изменениях соседних репозиториев выполни там `git diff --check` и проверь -затронутые ссылки. Markdown-only правки не требуют тестов всего приложения. +Markdown-only правки не требуют тестов приложения или соседнего checkout. Правило relative-links не проверяет внешние URL и HTML-ссылки. Mermaid-рендер в gate не входит; используй доступный локальный рендерер без отправки материалов @@ -169,13 +170,10 @@ npm run verify ### CI и обновления зависимостей `.gitea/workflows/verify.yml` запускает этот gate на push и PR с Node -из `.nvmrc` и установкой через `npm ci`. Checkout docs размещается внутри -workspace из `WealthLedger/main`, рядом с frontend, backend и specs. Соседние -репозитории для разработки берутся из `develop`; для `main`, PR в `main` -и `release/*` — из полных SHA в `.ci/refs.json`. SHA всех пяти участников -сохраняются в логе. -Ошибка checkout, Markdown или относительной ссылки завершает job с FAIL. -Это проверка ссылок на выбранные revisions, без Mermaid-render и semantic review. +из `.nvmrc` и установкой через `npm ci`. Скачивается только docs; +его SHA сохраняется в логе. Ошибка checkout, Markdown или внутренней ссылки +завершает job с FAIL. Внешние веб-ссылки — справочные; CI не проверяет их +доступность, Mermaid-render или semantic correctness. `security.yml` при push, PR и ежедневно проверяет всю Git-историю через Gitleaks 8.30.1 с redacted-выводом и все npm dependencies через @@ -187,4 +185,4 @@ Renovate объединяет minor/patch updates; major требует согл Dependency Dashboard, automerge не включён. Required checks включаются отдельно в Gitea; workflow сам по себе не запрещает merge. -Версии и ветки: [процесс релиза](../README.md#релизы-и-ветки). +Версии и ветки: [процесс релиза](README.md#релизы). diff --git a/README.md b/README.md index a7bab79..3bd3d73 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,12 @@ WealthLedger помогает частному инвестору понимат ## Границы знаний -[Основные спецификации](../specs/AI_INDEX.md) сохраняют согласованное проверяемое +[Основные спецификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AI_INDEX.md) сохраняют согласованное проверяемое поведение, а OpenSpec changes — предложения, дизайн и задачи изменений. Фактический Store находится в `specs/openspec/`, основные требования — в `specs/openspec/specs/`. Docs объясняет систему; код, тесты, схемы и конфигурация задают конкретный механизм, а [ADR](adr/README.md) сохраняют принятые решения. -Локальные инструкции [frontend](../frontend/AGENTS.md) и [backend](../backend/AGENTS.md) +Локальные инструкции [frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/AGENTS.md) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/AGENTS.md) описывают изменение реализации и её проверки. Эти источники дополняют друг друга. Найденный незавершённый change не доказывает @@ -67,3 +67,39 @@ WealthLedger помогает частному инвестору понимат Правила изменения текста, диаграмм, ссылок и проверки docs-impact находятся в [CONTRIBUTING](CONTRIBUTING.md). + +## Релизы + +Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые +релизы. Работа: `codex/` от актуального `develop`, PR в `develop`. +Обычные PR допускают squash; release и обратная синхронизация сохраняют историю. +Commits и PR titles — Conventional Commits, включая `spec:`. Версии вручную не повышай. + +1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Убедись, что + `develop` содержит последний релиз. Создай `release/` от проверенного + `develop`; `nvm use --silent`, `npm ci`. Hotfix начинается от `main` в + `release/hotfix-` и проходит тот же процесс. +2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog, + затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, + обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи + guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии. +3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку, + открой PR в `main` с заголовком `chore(release): X.Y.Z`. После зелёных + `verify`, `security`, `commits` выполни fast-forward-only merge. Если `main` + изменился, согласуй историю и повтори проверки кандидата. +4. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией + manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA: + `git tag -a vX.Y.Z -m "Release X.Y.Z"`, затем отправь только + `git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`. +5. Верни release-коммит через PR `main → develop` без squash/rebase; проверь, + что `develop` содержит тег. Опубликованные теги не перемещай. При сбое после + merge создай отсутствующий тег без нового bump; до merge исправь и проверь + существующий кандидат без повторного повышения версии. + +`feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch; +`!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):` +исключён из рекомендации и changelog. Конфигурация — `.release-it.json`. +В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход +администратором, обязательны `verify`, `security`, `commits`; теги `v*` защищены. +Workflow сам не включает эти настройки. Отдельное review approval для одного +владельца не требуется. Npm publishing, deploy и Gitea Releases не включены. diff --git a/architecture/cross-cutting/data-freshness.md b/architecture/cross-cutting/data-freshness.md index 8c35c2a..0615884 100644 --- a/architecture/cross-cutting/data-freshness.md +++ b/architecture/cross-cutting/data-freshness.md @@ -65,8 +65,8 @@ Client Cached Snapshot прежнего подключения больше не Пользовательский сценарий описан в [интеграции T-Bank](../../integrations/tbank.md). Согласованные правила frontend находятся в workspace-спецификациях -[query coherence](../../../specs/openspec/specs/frontend-query-coherence/spec.md) -и [data resilience](../../../specs/openspec/specs/frontend-data-resilience/spec.md); +[query coherence](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-query-coherence/spec.md) +и [data resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-data-resilience/spec.md); серверные правила — в -[portfolio](../../../specs/openspec/specs/portfolio-view/spec.md) и -[ledger](../../../specs/openspec/specs/brokerage-account-ledger/spec.md). +[portfolio](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) и +[ledger](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md). diff --git a/architecture/cross-cutting/ui-resilience.md b/architecture/cross-cutting/ui-resilience.md index 83f6f87..fbdebef 100644 --- a/architecture/cross-cutting/ui-resilience.md +++ b/architecture/cross-cutting/ui-resilience.md @@ -44,6 +44,6 @@ actions. Устаревший серверный Portfolio Snapshot показы [аутентификации](../../domains/authentication/overview.md). Согласованное поведение раскрыто в workspace-спецификациях -[UI resilience](../../../specs/openspec/specs/frontend-ui-resilience/spec.md), -[loading presentation](../../../specs/openspec/specs/frontend-loading-presentation/spec.md) -и [data resilience](../../../specs/openspec/specs/frontend-data-resilience/spec.md). +[UI resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-ui-resilience/spec.md), +[loading presentation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-loading-presentation/spec.md) +и [data resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-data-resilience/spec.md). diff --git a/architecture/http-api.md b/architecture/http-api.md index b957d84..8c2b91b 100644 --- a/architecture/http-api.md +++ b/architecture/http-api.md @@ -8,6 +8,15 @@ Backend генерирует OpenAPI из собственного публич схемы находятся вне frontend. Это разделение принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md) и [ADR 014](../adr/014-trusted-openapi-input.md). + +Backend командой `npm run openapi:generate` создаёт `openapi.json` без запуска +сервера и не хранит результат в Git. Contract test сравнивает вывод генератора +с Nest runtime schema настроенного приложения. Frontend хранит полученный +artifact у себя и проверяет соответствие generated types этой копии. CI не +скачивает соседние репозитории и не сравнивает их текущие revisions. Обновление +входного artifact потребителя выполняется отдельно с review; совместимость +развёрнутых версий не доказывается одним зелёным локальным CI. + API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по environment policy, а не используется frontend в runtime. @@ -36,7 +45,7 @@ message/stack. Типичный ответ: HTTP-ответа после всех стадий обработки запроса. **Известное расхождение:** согласованная -[спецификация аутентификации](../../specs/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках) +[спецификация аутентификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках) требует `pointer` и `detail` для невалидных полей. В текущей конфигурации валидация формирует строковые сообщения, а фильтр ожидает структурированные ошибки полей; поэтому корректность деталей `errors` не подтверждена. @@ -74,5 +83,5 @@ client request ID. Это позволяет сопоставить публич Выбор logger и граница ответственности описаны в [ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в workspace-спецификации -[Structured logging](../../specs/openspec/specs/backend-structured-logging/spec.md). +[Structured logging](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/backend-structured-logging/spec.md). Сборщик, срок хранения журналов и alerting не определяются приложением. diff --git a/design/overview.md b/design/overview.md index 6f1e7be..e8ab385 100644 --- a/design/overview.md +++ b/design/overview.md @@ -44,4 +44,4 @@ и [банковских картах](../domains/cards/overview.md). Согласованная общая основа описана в -[спецификации дизайн-системы](../../specs/openspec/specs/frontend-design-system/spec.md). +[спецификации дизайн-системы](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-design-system/spec.md). diff --git a/domains/accounts/overview.md b/domains/accounts/overview.md index 06b435c..7c551c5 100644 --- a/domains/accounts/overview.md +++ b/domains/accounts/overview.md @@ -93,7 +93,7 @@ sequenceDiagram [журнал операций](../operations/overview.md) или историю закрытых счетов. Для совокупного результата доступен [режим «Все счета»](../portfolio/all-accounts.md). -Согласованное поведение описывает [спецификация счетов](../../../specs/openspec/specs/brokerage-accounts/spec.md). +Согласованное поведение описывает [спецификация счетов](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-accounts/spec.md). Её раздел об экране пока требует одноколоночного списка и не описывает счётчики акций и облигаций. Текущая реализация и её проверки используют две колонки на широком экране и показывают эти счётчики. Расхождение требует diff --git a/domains/authentication/overview.md b/domains/authentication/overview.md index 6e8219f..0c00f5b 100644 --- a/domains/authentication/overview.md +++ b/domains/authentication/overview.md @@ -23,7 +23,7 @@ Access token действует 15 минут и хранится только в памяти браузера. Refresh token имеет серверный срок 7 дней, заменяется при восстановлении сессии и передаётся в cookie `refresh_token` с `httpOnly` и `SameSite=Lax`. -Сроки определены в [спецификации сессий](../../../specs/openspec/specs/user-authentication/spec.md#requirement-access-и-refresh-сессии) +Сроки определены в [спецификации сессий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-access-и-refresh-сессии) и [ADR 006](../../adr/006-auth-session-policy.md). После перезагрузки SPA получает новый access token через refresh-cookie. @@ -117,6 +117,6 @@ Backend проверяет учётные данные и tokens, хранит и проверку атомарности с реальной изолированной базой. Согласованное поведение находится в workspace-спецификации -[User authentication](../../../specs/openspec/specs/user-authentication/spec.md). +[User authentication](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md). Правила начального ожидания — в [состояниях интерфейса](../../architecture/cross-cutting/ui-resilience.md). diff --git a/domains/cards/overview.md b/domains/cards/overview.md index 5442ab7..2e8f648 100644 --- a/domains/cards/overview.md +++ b/domains/cards/overview.md @@ -140,6 +140,6 @@ JavaScript Number. Отдельный ввод ограничен signed64: на сохраняет новую финансовую историю и при откате приложения. Устройство хранения описано в [контейнерах](../../architecture/containers.md#хранимые-данные-и-границы-доступа). Согласованный контракт банковского учёта находится в -[manual-bank-cards](../../../specs/openspec/changes/add-manual-bank-cards/specs/manual-bank-cards/spec.md). +[manual-bank-cards](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/changes/add-manual-bank-cards/specs/manual-bank-cards/spec.md). Автоматическая загрузка банковских данных, кредитные карты, другие валюты, бюджеты и общий баланс банковских денег с инвестициями в эту версию не входят. diff --git a/domains/income/overview.md b/domains/income/overview.md index 19ba64e..3a1993c 100644 --- a/domains/income/overview.md +++ b/domains/income/overview.md @@ -59,7 +59,7 @@ для полного месяца это предыдущий полный месяц, иначе — непосредственно предшествующий интервал той же включительной длительности. Текущая страница его не показывает. Это расходится с -[основным требованием сравнений](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md#requirement-period-comparisons-preserve-calendar-and-currency-meaning), +[основным требованием сравнений](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md#requirement-period-comparisons-preserve-calendar-and-currency-meaning), которое описывает оба сравнения. Требуется отдельно согласовать этот разрыв; здесь сохранено различие между ответом API и фактическим экраном. @@ -104,7 +104,7 @@ Frontend проверяет принадлежность выплат по по Аналитика только читает данные, не хранит историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог. -Основное проверяемое поведение — в [спецификации дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md), +Основное проверяемое поведение — в [спецификации дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md), точная форма ответа — в [HTTP API](../../architecture/http-api.md). Не следует использовать календарь будущих событий для восстановления состава уже полученного дохода. diff --git a/domains/income/upcoming-events.md b/domains/income/upcoming-events.md index dd610d7..288ee49 100644 --- a/domains/income/upcoming-events.md +++ b/domains/income/upcoming-events.md @@ -29,7 +29,7 @@ Суммы рассчитаны по количеству на `calculatedAt`. Они не учитывают будущие сделки, право владения на дату реестра и персональный налоговый расчёт. Есть неразрешённое ограничение интерпретации: -[основная спецификация](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-forecasts-preserve-event-meaning-and-uncertainty) +[основная спецификация](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-forecasts-preserve-event-meaning-and-uncertainty) и текущая подсказка экрана называют прогноз суммой «до налогов», но для дивиденда реализация только умножает `dividend_net` на количество. Налоговый смысл этого поля не подтверждён локальными материалами; на @@ -61,7 +61,7 @@ API принимает необязательные `from` и `to` в форма Итоги остаются в ответе API; полный экран показывает список без общего итога. Текущие controller, сервис и экран поддерживают выбор периода, тогда как -[основная спецификация](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-account-page-presents-an-upcoming-events-list) +[основная спецификация](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-account-page-presents-an-upcoming-events-list) описывает фиксированные 30 дней и экран без фильтров. Документация отражает реализованный выбор периода, не изменяя это несогласованное требование. diff --git a/domains/instruments/bonds.md b/domains/instruments/bonds.md index f0e4b87..2568f1d 100644 --- a/domains/instruments/bonds.md +++ b/domains/instruments/bonds.md @@ -132,13 +132,13 @@ HTML-источник, общий кэш и поведение при измен выход очищает кэш сессии. Общие график, позиция и личные события имеют общее представление на страницах акций и облигаций. -Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md) -и [журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md) +Основные спецификации [портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) +и [журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md) описывают исходные финансовые понятия. Отдельная основная спецификация страницы облигации в текущем OpenSpec Store не найдена; условия вычислений -подтверждены [каталогом](../../../backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts), -[рыночными данными](../../../backend/src/modules/investments/instruments/bonds/application/bond-market.service.ts), -[выплатами](../../../backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts) -и [проверками](../../../backend/src/modules/investments/instruments/bonds/application/bond.spec.ts). +подтверждены [каталогом](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts), +[рыночными данными](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-market.service.ts), +[выплатами](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-income.service.ts) +и [проверками](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond.spec.ts). Границы источников описаны в [разделе интеграций](../../integrations/README.md), сходное разделение рыночных и личных данных — в [акциях](stocks.md). diff --git a/domains/instruments/stocks.md b/domains/instruments/stocks.md index aaababa..fc7ec0f 100644 --- a/domains/instruments/stocks.md +++ b/domains/instruments/stocks.md @@ -169,11 +169,11 @@ TTM означает последние 12 месяцев, MRQ — послед История открытых счетов не обещает полную историю владения, включая закрытые счета, переводы и универсальные корпоративные события. -Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md) -и [журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md) +Основные спецификации [портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) +и [журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md) задают исходные финансовые понятия. Отдельная основная спецификация страницы акции в текущем OpenSpec Store не найдена; описанные механизмы проверяются -по [каталогу](../../../backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts), -[рыночным данным](../../../backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts), -[выплатам](../../../backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](../../../backend/src/modules/investments/instruments/stocks/application/stock.spec.ts). +по [каталогу](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts), +[рыночным данным](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-market.service.ts), +[выплатам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock.spec.ts). Сопоставление с облигациями — на [странице облигаций](bonds.md). diff --git a/domains/operations/overview.md b/domains/operations/overview.md index 72bcdff..e215768 100644 --- a/domains/operations/overview.md +++ b/domains/operations/overview.md @@ -112,6 +112,6 @@ Backend хранит не более 100 страниц в памяти проц и `TBANK_UNAVAILABLE` сохраняют [общую семантику API](../../architecture/http-api.md). Пустой успешный ответ означает отсутствие подходящих операций, а не сбой. -Согласованное поведение описано в [спецификации журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md). +Согласованное поведение описано в [спецификации журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md). Фильтр конкретной бумаги и общий режим расширяют исходное описание отдельного счёта; их механизм раскрыт на страницах инструментов и общего портфеля. diff --git a/domains/portfolio/all-accounts.md b/domains/portfolio/all-accounts.md index 0edf18b..320bb52 100644 --- a/domains/portfolio/all-accounts.md +++ b/domains/portfolio/all-accounts.md @@ -109,11 +109,11 @@ Backend публикует статические маршруты под `/api/ работать с отдельными счетами. При откате сначала возвращается клиент. Этот механизм не требует изменения схемы SQLite и миграции финансовых данных. -Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md), -[операций](../../../specs/openspec/specs/brokerage-account-ledger/spec.md), -[дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md) -и [событий](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md) +Основные спецификации [портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md), +[операций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md), +[дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md) +и [событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md) описывают отдельный счёт. Общий режим подтверждён действующими маршрутизаторами, -сервисами и [проверками агрегации](../../../backend/src/modules/investments/all-accounts.spec.ts), +сервисами и [проверками агрегации](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/all-accounts.spec.ts), но не выделен в самостоятельную основную спецификацию. Не следует считать описание общего режима изменением требований к отдельному счёту. diff --git a/domains/portfolio/data-reference.md b/domains/portfolio/data-reference.md index 9ee29a8..c976463 100644 --- a/domains/portfolio/data-reference.md +++ b/domains/portfolio/data-reference.md @@ -90,8 +90,8 @@ frontend показывает до сотых. Положительное зна поэтому ограничение долей действует на все валюты. В общем портфеле проверка отрицательных денег выполняется по остаткам счетов до взаимозачёта. -Нормативные основы: [нормализованный состав](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio) -и [точные значения и валютные агрегаты](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты). +Нормативные основы: [нормализованный состав](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio) +и [точные значения и валютные агрегаты](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты). Структура и общий режим подтверждены реализацией и её проверками; отдельных требований к ним в основной спецификации портфеля пока нет. Поля транспорта следует сверять с [HTTP API](../../architecture/http-api.md), diff --git a/domains/portfolio/flows/portfolio-loading.md b/domains/portfolio/flows/portfolio-loading.md index 875818c..fdccd25 100644 --- a/domains/portfolio/flows/portfolio-loading.md +++ b/domains/portfolio/flows/portfolio-loading.md @@ -79,9 +79,9 @@ sequenceDiagram | Метаданные инструмента | Успешный поиск или подтверждённое отсутствие | Свежими считаются 24 часа; общий кэш на 500 ключей UID/FIGI, ошибки не сохраняются | Это параметры текущей реализации, а не самостоятельная политика этого -документа: [снимки](../../../../backend/src/modules/investments/portfolio/application/portfolio.service.ts), -[метаданные](../../../../backend/src/integrations/tbank/instrument-metadata.service.ts) и -[клиентский запрос](../../../../frontend/src/entities/portfolio/api/usePortfolio.ts). +документа: [снимки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/application/portfolio.service.ts), +[метаданные](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/instrument-metadata.service.ts) и +[клиентский запрос](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/entities/portfolio/api/usePortfolio.ts). Пять минут — предельный возраст успешного снимка, а не дополнительные пять минут после окончания свежих 30 секунд. Запасной ответ сохраняет @@ -152,7 +152,7 @@ Frontend отображает название и остальные сведе источника; его увеличение не является гарантированным исправлением ограничения самого T-Bank. -Проверяемые правила: [свежесть и запасной снимок](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot), -[клиентские состояния](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-состояния-и-ошибки-portfolio-на-клиенте). +Проверяемые правила: [свежесть и запасной снимок](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot), +[клиентские состояния](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-состояния-и-ошибки-portfolio-на-клиенте). Для расследования конкретного значения используйте [порядок диагностики](../how-to/investigate-discrepancy.md). diff --git a/domains/portfolio/how-to/investigate-discrepancy.md b/domains/portfolio/how-to/investigate-discrepancy.md index ce48d7d..687889b 100644 --- a/domains/portfolio/how-to/investigate-discrepancy.md +++ b/domains/portfolio/how-to/investigate-discrepancy.md @@ -37,16 +37,16 @@ теряет цифры, валюту или показатель, исследуйте frontend. Если исходные безопасные данные и нормализованный ответ расходятся, исследуйте backend. Если не совпадают ожидаемое и описанное поведение, - сначала сопоставьте [спецификацию портфеля](../../../../specs/openspec/specs/portfolio-view/spec.md) + сначала сопоставьте [спецификацию портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) и ограничения [потока загрузки](../flows/portfolio-loading.md). Исправление документации само по себе не разрешает конфликт требований и реализации. Для адресной проверки исходников владельцами служат -[сервис снимков](../../../../backend/src/modules/investments/portfolio/application/portfolio.service.ts), -[валютные итоги](../../../../backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts), -[структура](../../../../backend/src/modules/investments/portfolio/domain/portfolio-structure.ts) и -[подготовка экрана](../../../../frontend/src/pages/account/ui/AccountPage.vm.ts). +[сервис снимков](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/application/portfolio.service.ts), +[валютные итоги](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts), +[структура](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/domain/portfolio-structure.ts) и +[подготовка экрана](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/pages/account/ui/AccountPage.vm.ts). Это точки продолжения расследования; смысл вычислений изложен выше. Разбор завершён, когда различие объяснено составом, временем или определением diff --git a/domains/portfolio/overview.md b/domains/portfolio/overview.md index 53f8b3d..b6c56b3 100644 --- a/domains/portfolio/overview.md +++ b/domains/portfolio/overview.md @@ -64,7 +64,7 @@ SQLite не хранит сам портфель: backend получает да без логотипа и с подписью результата. Отсутствующие показатели скрываются, а не подменяются нулём или заглушкой. -Существует расхождение: [согласованная спецификация представления позиций](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте) +Существует расхождение: [согласованная спецификация представления позиций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте) требует доступности всех иных типов в отдельной секции, а текущие frontend и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции остаются в ответе, валютных итогах, структуре и знаменателе долей остальных @@ -95,5 +95,5 @@ SQLite не хранит сам портфель: backend получает да Для проверки конкретной суммы используйте [руководство по расхождениям](how-to/investigate-discrepancy.md). Проверяемое поведение отдельного счёта описано в -[спецификации портфеля](../../../specs/openspec/specs/portfolio-view/spec.md); +[спецификации портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md); точную форму HTTP-ответа задаёт [OpenAPI backend](../../architecture/http-api.md). diff --git a/integrations/moex.md b/integrations/moex.md index 65328a1..5e85a6f 100644 --- a/integrations/moex.md +++ b/integrations/moex.md @@ -48,6 +48,6 @@ HTTP-запрос ограничен таймаутом. Сеть, неуспе Наличие успешного HTTP-ответа не гарантирует котировку без задержки. Условия обработки источника в прогнозе закреплены в -[спецификации будущих событий](../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results). +[спецификации будущих событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results). Точный механизм транспорта, разбора и сопоставления реализован в -[клиенте MOEX](../../backend/src/integrations/moex/moex.client.ts). +[клиенте MOEX](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/moex/moex.client.ts). diff --git a/integrations/nkr.md b/integrations/nkr.md index 3711f41..741d585 100644 --- a/integrations/nkr.md +++ b/integrations/nkr.md @@ -36,7 +36,7 @@ ISIN, даты и ссылки на выпуск. Изменение табли После ошибки новая загрузка ограничена минутным интервалом; внутри одного HTTP-запроса повторных попыток нет. Обновление запускается следующим обращением после истечения кэша, без фонового задания. Точный механизм и сроки принадлежат -[клиенту НКР](../../backend/src/integrations/ncr/ncr-ratings.client.ts). +[клиенту НКР](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/ncr/ncr-ratings.client.ts). При отказе реестра показатели облигации отмечают недоступность рейтинга, сохраняя остальные доступные показатели. Готовый ответ метрик имеет diff --git a/integrations/tbank.md b/integrations/tbank.md index 1c7a582..76c6efb 100644 --- a/integrations/tbank.md +++ b/integrations/tbank.md @@ -36,7 +36,7 @@ Backend передаёт имя файла `logoName` из метаданных Логотипы используются в портфеле, операциях, будущих событиях, поиске и страницах инструментов. Отсутствующее имя или ошибка изображения оставляют буквенную заглушку и не меняют финансовые значения. Правило построения URL -принадлежит [форматированию frontend](../../frontend/src/shared/lib/display.ts). +принадлежит [форматированию frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/shared/lib/display.ts). ## Проверка и сохранение подключения @@ -96,7 +96,7 @@ sequenceDiagram для последующих запросов. Оно не описывает отзыв token на стороне T-Bank. Согласованное поведение сохранено в workspace-спецификации -[T-Bank connection](../../specs/openspec/specs/tbank-connection/spec.md). +[T-Bank connection](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/tbank-connection/spec.md). Точные endpoints и формы ответов определяет backend OpenAPI; [HTTP API](../architecture/http-api.md) описывает общую границу. @@ -126,7 +126,7 @@ Backend хранит token в шифротексте AES-256-GCM с новым n | `TBANK_CA_CERT_PATH` | Необязательный путь к доверенному корневому CA в PEM для сетей с TLS-инспекцией; проверка TLS сохраняется | Параметры окружения определяет backend -[validateEnv](../../backend/src/config/env.validation.ts) (workspace-ссылка). +[validateEnv](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/config/env.validation.ts) (справочный источник). Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50 ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED` обычный RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд, @@ -139,7 +139,7 @@ Backend хранит token в шифротексте AES-256-GCM с новым n отсутствие инструмента, а не временный сбой. Остальные RPC при полной очереди завершаются сразу. Параметры попыток принадлежат -[T-Bank client](../../backend/src/integrations/tbank/tbank.client.ts) (workspace-ссылка); +[T-Bank client](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/tbank.client.ts) (справочный источник); решение об ограниченных повторах — [ADR 005](../adr/005-tbank-upstream-resilience.md). Большое число ранее неизвестных инструментов требует отдельных запросов -- 2.54.0 From cead9dc1765700b7e10f9567e38f6d68d4ec9656 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 16 Sep 2026 09:41:58 +0300 Subject: [PATCH 2/3] docs: document container deployment and recovery --- README.md | 1 + adr/001-prisma-sqlite-local-mvp.md | 3 + adr/015-single-server-sqlite-deployment.md | 47 +++++ adr/README.md | 1 + architecture/README.md | 1 + architecture/containers.md | 53 ++++- architecture/deployment.md | 227 +++++++++++++++++++++ 7 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 adr/015-single-server-sqlite-deployment.md create mode 100644 architecture/deployment.md diff --git a/README.md b/README.md index 3bd3d73..c48ab8c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ WealthLedger помогает частному инвестору понимат | Понять ошибку или старые данные | [Состояния интерфейса](architecture/cross-cutting/ui-resilience.md), [свежесть данных](architecture/cross-cutting/data-freshness.md) | | Понять визуальные роли и состояния | [Визуальные принципы](design/overview.md) | | Узнать причину архитектурного решения | [Журнал ADR](adr/README.md) | +| Запустить, обновить или восстановить приложение | [Контейнерная поставка](architecture/deployment.md) | ## Границы знаний diff --git a/adr/001-prisma-sqlite-local-mvp.md b/adr/001-prisma-sqlite-local-mvp.md index 1d9b557..1c00b6b 100644 --- a/adr/001-prisma-sqlite-local-mvp.md +++ b/adr/001-prisma-sqlite-local-mvp.md @@ -2,6 +2,9 @@ **Status:** Accepted, baseline decision +Deployment boundary расширена [ADR 015](015-single-server-sqlite-deployment.md) +для одного сервера и одного backend process. Ниже сохранён исходный контекст. + ## Context MVP нужен воспроизводимый локальный доступ к данным без инфраструктуры развёртывания. Доменные таблицы и миграции на baseline отсутствуют. diff --git a/adr/015-single-server-sqlite-deployment.md b/adr/015-single-server-sqlite-deployment.md new file mode 100644 index 0000000..c8c56c9 --- /dev/null +++ b/adr/015-single-server-sqlite-deployment.md @@ -0,0 +1,47 @@ +# 015. SQLite в ограниченном single-server deployment + +**Status:** Accepted + +## Context + +[ADR 001](001-prisma-sqlite-local-mvp.md) ограничивал SQLite локальным MVP. +Контейнерная поставка требует постоянного хранилища на одном сервере, +но не требует высокой доступности или нескольких экземпляров API. +Пользователи, сессии, зашифрованные подключения и банковская история уже +хранятся в SQLite. Кэши, limiter и generation подключений локальны процессу. + +## Decision + +Разрешён один backend process на одном Docker host с SQLite в постоянном +host directory. Development использует отдельный named volume. SQLite +остаётся embedded library без сетевого порта. Это расширяет deployment +boundary ADR 001; его исторический контекст сохраняется. + +Prisma migrations выполняются отдельным одноразовым service из того же image, +что и backend. Перед изменением схемы backend останавливается, всё data +directory копируется вместе с возможными WAL/SHM files. Ключ шифрования +сохраняется отдельно. Неуспешная migration блокирует запуск новой версии. + +Откат image требует совместимости с текущей схемой и не восстанавливает +старую базу. Restore требует отдельного решения о потере данных после backup. +Frontend и backend имеют независимые версии; production фиксирует version tags. +Краткий простой допустим. + +## Alternatives + +PostgreSQL потребовал бы миграции данных и отдельного database lifecycle; +текущие требования этого не оправдывают. Kubernetes, replicas и blue-green +не устраняют ограничения process-local состояния и не входят в решение. + +## Consequences + +Сервер не обещает HA или zero-downtime. Один backend instance — обязательное +ограничение, а не рекомендуемая настройка масштабирования. Нужны контроль +свободного места, согласованные backups и проверяемое восстановление. + +Решение пересматривается при необходимости нескольких replicas, другом +availability target, заметном write contention или потребности в отдельном +database service. Добавление реплик до такого решения недопустимо. + +Топология описана в [архитектуре](../architecture/containers.md#контейнерная-поставка), +операции — в [runbook](../architecture/deployment.md). diff --git a/adr/README.md b/adr/README.md index 87efa5f..8413903 100644 --- a/adr/README.md +++ b/adr/README.md @@ -19,3 +19,4 @@ - [009 — Concurrent refresh rotation policy](009-concurrent-refresh-rotation-policy.md) - [013 — НКР как первый источник кредитных рейтингов облигаций](013-nkr-bond-credit-ratings.md) - [014 — Актуальная OpenAPI-схема как вход frontend](014-trusted-openapi-input.md) +- [015 — SQLite в ограниченном single-server deployment](015-single-server-sqlite-deployment.md) diff --git a/architecture/README.md b/architecture/README.md index 67cb524..3f11f14 100644 --- a/architecture/README.md +++ b/architecture/README.md @@ -16,6 +16,7 @@ | Как обрабатываются ошибки и ожидание? | [HTTP API и ошибки](http-api.md), [состояния интерфейса](cross-cutting/ui-resilience.md) | | Как связать ошибку с обработкой запроса? | [Наблюдаемость](http-api.md#наблюдаемость) | | Как устроено подтверждённое локальное окружение? | [Container](containers.md#представление-container), [качество и ограничения](containers.md#качество-и-ограничения) | +| Как запустить, обновить или восстановить приложение? | [Развёртывание](deployment.md) | | Что означают предметные термины? | [Глоссарий](../glossary.md) | Эта карта покрывает вопросы архитектуры там, где о них есть подтверждённое diff --git a/architecture/containers.md b/architecture/containers.md index a5e2f13..99761a4 100644 --- a/architecture/containers.md +++ b/architecture/containers.md @@ -27,11 +27,50 @@ flowchart LR F -->|"HTTPS; изображения без авторизации"| C ``` -SPA выполняется в браузере пользователя. Backend — отдельное локальное +SPA выполняется в браузере пользователя. Backend — отдельное приложение; SQLite используется внутри его процесса через библиотеку и файл базы. Между API и SQLite нет сетевого database server. Граница системы на схеме показывает принадлежность частей, а не их размещение на одном узле. +## Контейнерная поставка + +Deployment view для одного сервера. Caddy принадлежит серверу; Compose +содержит два постоянных application containers и одноразовую migration. + +```mermaid +flowchart LR + Browser["Браузер · React SPA"] -->|HTTPS| Caddy["Серверный Caddy · TLS"] + subgraph Host["Один Docker host"] + Caddy -->|"HTTP · 127.0.0.1:8080"| Frontend["frontend · nginx · SPA + /api/"] + Frontend -->|"HTTP /api/v1 · internal network"| Backend["backend · NestJS · один process"] + Migration["migration · Prisma · one-shot"] -->|"До старта backend"| Data[("Host directory · SQLite + WAL")] + Backend -->|"Чтение и запись /data"| Data + Secrets["Restricted environment · ключи вне images"] --> Backend + end +``` + +Только frontend публикует host port, привязанный к loopback. Backend не +публикует порт, но имеет исходящий доступ к внешним API. nginx сохраняет +`/api/` prefix, forwarding headers и request ID; Docker DNS разрешается +повторно после backend recreation. HTML требует revalidation, hashed assets +имеют immutable cache. Caddy не содержит отдельного маршрута к API. + +Development заменяет nginx на Vite с HMR, backend — на Nest watch, а host data +path — на отдельный named volume. Browser origin остаётся единым. Linux +node_modules не монтируются с хоста. Production SPA собирается с relative API +base; production cookies остаются Secure, публичный origin использует HTTPS. + +Компоненты имеют независимые images и версии. Production фиксирует image +version tags; migration использует тот же backend image. Успех migration предшествует +backend healthcheck на существующем auth route; frontend ждёт healthy backend. +Runtime работает non-root, SQLite сохраняется вне image, ключ шифрования и JWT +secret поступают из restricted environment и сохраняются между обновлениями. + +Ограничение SQLite расширено в [ADR 015](../adr/015-single-server-sqlite-deployment.md). +[Runbook](deployment.md) описывает установку, independent +updates, backup, rollback и explicit restore. Он также перечисляет внешние +prerequisites: локальный smoke не подтверждает registry/server rollout. + ## Ответственность частей | Часть | Ответственность | @@ -106,10 +145,10 @@ credentials не являются сохраняемым представлен ## Сохранение и восстановление данных -Резервная копия SQLite должна быть согласованной: для работающей базы -используется SQLite backup API с учётом WAL. Несогласованное копирование -файлов базы и WAL не сохраняет целостный снимок. Восстановление проверяется -на отдельной копии. Ключ шифрования подключения хранится отдельно от базы; +Поддерживаемая процедура останавливает backend и копирует весь data directory +с возможными WAL/SHM files до migration. Копирование отдельных файлов +работающей базы не сохраняет целостный снимок; для live backup потребовался бы +SQLite backup API. Восстановление проверяется на отдельной копии. Ключ шифрования подключения хранится отдельно от базы; его потеря делает сохранённый T-Bank token нечитаемым. Откат приложения требует совместимости предыдущей версии с сохранённой @@ -150,12 +189,12 @@ access token и вращаемый refresh token. [Политика query-кэш отмечены в [аутентификации](../domains/authentication/overview.md#регистрация-и-вход) и [HTTP API](http-api.md#error-contract). -Подтверждённая топология — локальная SPA и локальный API с файлом SQLite. +Поддерживается локальный Compose и ограниченная single-server topology с SQLite. Origin браузера, адрес API и cookie policy должны быть согласованы. `localhost` и `127.0.0.1` задают разные browser-границы для cookie и CORS. Локальный HTTP поддерживается в development-режиме backend; в остальных режимах refresh-cookie имеет Secure flag и требует защищённого соединения. -Наличие локального запуска не задаёт production-топологию или гарантии SLA. +Single-server deployment не задаёт гарантий HA или zero-downtime. Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования подключений и согласование изменений подключения между распределёнными diff --git a/architecture/deployment.md b/architecture/deployment.md new file mode 100644 index 0000000..5097112 --- /dev/null +++ b/architecture/deployment.md @@ -0,0 +1,227 @@ +# Контейнерная поставка + +Runbook для одного Docker host, одного backend и краткого простоя. +[Топология](containers.md#контейнерная-поставка) и +[ADR 015](../adr/015-single-server-sqlite-deployment.md) объясняют границы. +Команды выполняются из checkout `WealthLedger/main`, содержащего `compose.yaml`. +Development дополнительно требует checkout `frontend/` и `backend/`. + +## Предпосылки + +- Docker Engine с Compose plugin **не ниже 2.24.4** (`!override` в development), + доступ к registry и достаточно свободного диска для non-root UID 1000. + Локально проверены Engine 29.8.0, Compose 5.5.1, Linux arm64. + Другую архитектуру проверяют отдельно; multi-platform images не обещаны. +- Gitea Container Registry; проверяемая версия сервера — 1.27.3. + Runner label — `ubuntu-latest`, runner должен иметь Docker CLI, buildx и daemon. + Наличие label само по себе не доказывает Docker capability. +- В Actions каждого component repo нужны `REGISTRY_USERNAME` и + `REGISTRY_TOKEN` отдельной identity с package write permission. Server + использует другую identity только с package read permission. Built-in + workflow token не заменяет эту настройку. +- На сервере Caddy уже завершает TLS и передаёт весь домен на + `127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет. + DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook. + +Реальная публикация и server pull-only login требуют отдельной проверки +на вашей инфраструктуре. Локальные images не доказывают registry permissions. + +## Development + +Из workspace с исходниками обоих компонентов: + +```bash +docker version +docker compose version +docker compose --env-file .env.development.example \ + -f compose.yaml -f compose.development.yaml config --quiet +docker compose --env-file .env.development.example \ + -f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180 +``` + +Откройте `http://localhost:5173`. Vite отдаёт HMR и проксирует `/api/` без +rewrite; Nest работает в watch mode. Монтируются только source/public, +`node_modules` и generated files остаются в container filesystem. `.env` +с хоста не копируется. После изменения manifest, lockfile, tsconfig или Vite +config пересоберите соответствующий image. `up --build` обновляет Linux +dependencies без stale named volume для `node_modules`. + +Для другого порта скопируйте `.env.development.example` в игнорируемый +`.env.development` и согласованно поменяйте `FRONTEND_PORT`/`FRONTEND_ORIGIN`. +Пример содержит только вымышленные local secrets; server secrets сюда не входят. +Поля image/data directory нужны для интерполяции base Compose, но полностью +заменяются development override. В итоговом config нет production data path. + +Project `wealthledger-dev` хранит SQLite в `development-data`. Обычные +`restart` и `down` сохраняют его. Не применяйте `down -v` к данным, которые +хотите сохранить. Не используйте production project name для development. + +## Публикация компонентных images + +Штатный release process каждого repo создаёт защищённый `vX.Y.Z` после +проверенного release commit. `image.yml` проверяет tag против package/lock +version, затем выполняет `npm ci`, `npm run verify`; backend дополнительно +выполняет `npm run test:e2e`. У каждого image OCI version/revision labels. + +Images публикуются как `git.personal.ksv741.ru/wealthledger/frontend:X.Y.Z` +и `.../backend:X.Y.Z`. `latest` не создаётся. +Workflow отказывается перезаписывать существующие release tags; +защита release tags и запрет параллельных publishers обязательны. Если одна +публикация уже состоялась, не удаляйте tag для повторного запуска: проверьте +опубликованный image и завершите проверку существующего artifact отдельно. + +Выберите опубликованные version tags, выполните pull и сравните OCI revision +с release commit. Выполните smoke с disposable data/config до первого server +rollout. В production env хранится пара полных image references, например +`git.personal.ksv741.ru/wealthledger/frontend:0.1.0`. Сохраните versions/revisions +в deployment record. Release tags не должны переназначаться другим images. + +## Первая установка + +Создайте restricted environment file и data directory: + +```bash +sudo install -d -m 0700 /srv/wealthledger +sudo install -d -m 0700 -o 1000 -g 1000 /srv/wealthledger/data +sudo install -m 0600 .env.compose.example /srv/wealthledger/production.env +``` + +Дальнейшие команды выполняет оператор, имеющий доступ к environment file, +backup directory и Docker. Заполните env локальным защищённым редактором: +полные ссылки `FRONTEND_IMAGE` и `BACKEND_IMAGE` с опубликованными version tags, +публичный HTTPS origin и абсолютный data path. Версии в примере — образец +формата; перед запуском выберите реально опубликованные releases. +Пустые required значения блокируют config; некорректные secrets блокируют backend. +Не выводите полный rendered config с настоящими secrets в журналы. + +Для новой базы создайте JWT secret длиной хотя бы 32 символа и отдельный +32-byte encryption key в base64 (`openssl rand -hex 32` и +`openssl rand -base64 32` в защищённом терминале). Сохраните ключ отдельно +от data backup. Для существующей установки перенесите прежние JWT secret +и encryption key, а согласованную копию **всего** data directory — при +остановленном прежнем backend. Файл должен называться `wealthledger.db`; +если переименовываете, согласуйте также WAL/SHM companion names до старта. +Не запускайте старый и новый backend на одной базе одновременно. + +Войдите в registry server identity без помещения token в командную строку: + +```bash +docker login git.personal.ksv741.ru --username SERVER_PULL_USER +export COMPOSE_FILE=compose.yaml +export COMPOSE_PROJECT_NAME=wealthledger +export COMPOSE_ENV_FILES=/srv/wealthledger/production.env +docker compose config --quiet +docker compose pull frontend backend migration +docker compose up -d --wait --wait-timeout 120 +docker compose ps -a +``` + +Migration и backend используют один image и один data mount. +Migration должна завершиться с кодом 0; иначе backend не запускается. +Readiness принимает 401 от существующего `/api/v1/auth/me` без credentials. +Frontend становится healthy после backend. Backend host port отсутствует. +Runtime non-root; root filesystem read-only, nginx temp files находятся в tmpfs. + +Проверьте SPA deep link, регистрацию/вход, refresh, существующие карты, +чтение старого зашифрованного подключения и один финансовый запрос. +Production refresh-cookie имеет Secure flag: проверяйте через HTTPS Caddy; +loopback HTTP curl без browser cookie jar не доказывает browser session. +При локальном smoke используйте disposable credentials и явную передачу cookie. +Направляйте Caddy на новый deployment только после успешной проверки. +Прежний runtime удаляйте только после проверки новой установки. + +## Backup при остановленном backend + +До backend update проверьте совместимость новой версии с текущим frontend +и схемой, а обратной версии — с новой схемой. При неизвестной совместимости +не обещайте image rollback. В выбранном rollout отсутствуют параллельные API. + +Ниже data path соответствует примеру env; при другом path замените его. +Сохраните текущую пару image references/revisions и отдельно — ключи. + +```bash +docker compose stop backend +docker compose ps -a backend +install -d -m 0700 /srv/wealthledger/backups +BACKUP_DIRECTORY="/srv/wealthledger/backups/$(date -u +%Y%m%dT%H%M%SZ)" +mkdir -m 0700 "$BACKUP_DIRECTORY" +cp -a /srv/wealthledger/data "$BACKUP_DIRECTORY/data" +``` + +Перед копированием убедитесь, что backend остановлен, migration не выполняется +и никто другой не пишет в каталог. Успех `cp` обязателен перед продолжением. +Не копируйте отдельный `.db` из работающего приложения. Проверяйте backup +на отдельной копии: migration status, `PRAGMA integrity_check`, auth, карты +и расшифровка тестового подключения исходным ключом. Backup без проверяемого +ключа недостаточен для восстановления подключения. + +## Независимое обновление frontend + +После проверки API compatibility замените только `FRONTEND_IMAGE` в env: + +```bash +docker compose pull frontend +docker compose up -d --no-deps --wait --wait-timeout 120 frontend +``` + +Backend container и SQLite не пересоздаются. Входная точка кратко недоступна. +Проверьте deep link, login/refresh и новую OCI revision. Старым browser tabs +может понадобиться reload: старые hashed chunks не сохраняются между images. + +## Независимое обновление backend + +Выполните stopped-backend backup выше. Замените только `BACKEND_IMAGE` в env. +Последовательность не продолжается после failed migration: + +```bash +docker compose pull backend migration +docker compose up --no-deps --force-recreate --abort-on-container-exit \ + --exit-code-from migration migration && +docker compose up -d --no-deps --wait --wait-timeout 120 backend +``` + +Frontend не пересоздаётся; nginx повторно разрешает backend DNS. Во время +перерыва возможен 502. При failure оставьте backend остановленным, сохраните +диагностику migration и выберите совместимый image либо отдельный restore. +Не используйте `migrate reset` или автоматический `resolve` как recovery. +Проверьте migration status, health, login/refresh, карты и расшифровку подключения. +После успешного smoke запишите фактически установленную пару image references. + +## Откат image + +Для frontend верните предыдущий совместимый version tag и повторите frontend update. +Для backend сначала остановите процесс и подтвердите совместимость старого +image с **текущей** схемой. Верните предыдущий image reference и запустите только backend: + +```bash +docker compose pull backend +docker compose up -d --no-deps --wait --wait-timeout 120 backend +``` + +Данные и encryption key не заменяются. Migration service здесь намеренно +не запускается: старые migrations не являются down-migrations. Прочитайте +новые записи, сделанные после backup, и проверьте auth/cards/connection. +При несовместимости оставьте приложение остановленным до решения о recovery. + +## Explicit restore + +Это отдельное решение оператора, **не часть image rollback**. Оператор выбирает +backup timestamp и явно принимает потерю всех изменений после него, включая +сессии, карты и подключения. Сначала воспроизведите restore на isolated project +с отдельным data path/port и совместимым image; не используйте production data +как fixture. Убедитесь в наличии соответствующего encryption key. + +После отдельного решения остановите backend и убедитесь, что migration не +работает. Сохраните нынешний каталог под новым именем, ничего не удаляя: + +```bash +docker compose stop backend +mv /srv/wealthledger/data /srv/wealthledger/data-before-restore-TIMESTAMP +cp -a /srv/wealthledger/backups/CHOSEN_TIMESTAMP/data /srv/wealthledger/data +``` + +Проверьте owner 1000:1000, permissions, выбранный compatible image и исходный +ключ. Запустите backend командой rollback с `--no-deps`, затем выполните smoke. +Сверьте наличие записей до backup и отсутствие известных записей потерянного +интервала. Сохраните прежний каталог для расследования. При необходимости +новой migration сначала выполните обычную backup/update последовательность. -- 2.54.0 From 34b483018b0890814a6fe00ae45ead27fac1ebab Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 16 Sep 2026 11:35:55 +0300 Subject: [PATCH 3/3] chore(release): 0.1.1 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 773ff10..f90f09a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.1.1](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.0...v0.1.1) (2026-09-16) + +### Documentation + +* document container deployment and recovery ([cead9dc](https://git.personal.ksv741.ru/WealthLedger/docs/commit/cead9dc1765700b7e10f9567e38f6d68d4ec9656)) +* make documentation checks self-contained ([97069d6](https://git.personal.ksv741.ru/WealthLedger/docs/commit/97069d6f6d0719d6fcee30b1ce8adbda41bd3a6d)) + ## 0.1.0 (2026-09-14) ### Features diff --git a/package-lock.json b/package-lock.json index b925340..6df3061 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wealth-ledger-docs", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wealth-ledger-docs", - "version": "0.1.0", + "version": "0.1.1", "devDependencies": { "@commitlint/cli": "21.2.2", "@commitlint/config-conventional": "21.2.2", diff --git a/package.json b/package.json index 1bf5677..0519dd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wealth-ledger-docs", - "version": "0.1.0", + "version": "0.1.1", "private": true, "engines": { "node": "24.21.0" -- 2.54.0