Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc030f84c9 | ||
|
|
f27487b6a8 | ||
|
|
9fbc23f757 | ||
|
|
80fa254a31 | ||
|
|
21853fde6a | ||
|
|
7e382d460b | ||
|
|
95e4361599 | ||
|
|
4a78dd640d | ||
|
|
93dd5539ad |
@@ -0,0 +1,65 @@
|
|||||||
|
# Code health и границы изменения
|
||||||
|
|
||||||
|
## Search → reuse → converge
|
||||||
|
|
||||||
|
Перед utility, hook, wrapper, dependency или новым technical pattern:
|
||||||
|
|
||||||
|
1. Проследи поведение, инварианты и callers у ответственного owner.
|
||||||
|
2. Ищи аналоги по имени **и реализации** (`rg` по imports, API, выражению).
|
||||||
|
3. Расширь подходящий owner/helper/pattern; выбирай минимальное решение,
|
||||||
|
полностью закрывающее текущую задачу. Новый файл сам по себе не улучшение.
|
||||||
|
|
||||||
|
Уменьшай количество способов решать одну техническую задачу: конфигурацию
|
||||||
|
release-it и commitlint поддерживай средствами этих инструментов. Сходный синтаксис
|
||||||
|
без общей ответственности не требует DRY. Критерий dependency — в
|
||||||
|
[dependencies](dependencies.md).
|
||||||
|
|
||||||
|
## Change scope
|
||||||
|
|
||||||
|
Consistency с подходящим project pattern предпочтительнее универсальности.
|
||||||
|
Новые layers, wrappers, generic helpers, factories, schemas, validators, scripts
|
||||||
|
и дополнительные проверки требуют текущей ответственности или риска.
|
||||||
|
Сначала используй native API/установленный stack; не строй на гипотетических
|
||||||
|
consumers и будущих requirements.
|
||||||
|
|
||||||
|
Не вводи shared tooling для других репозиториев как обязательную зависимость
|
||||||
|
их сборки, CI или маршрута инструкций. Каждый repo владеет своими checks.
|
||||||
|
Trust-boundary validation, authorization, atomicity, accessibility и необходимая
|
||||||
|
regression proof остаются частью полного решения.
|
||||||
|
|
||||||
|
## Boy Scout review
|
||||||
|
|
||||||
|
После изменения проверь affected area: новое дублирование, пропущенный helper,
|
||||||
|
competing patterns, небольшие связанные legacy issues и conventions.
|
||||||
|
Исправляй в рамках задачи только непосредственно связанные, небольшие,
|
||||||
|
low-risk, behavior-preserving проблемы, которые легко проверить.
|
||||||
|
Более широкие находки сохраняй для отдельной migration; массовый cleanup
|
||||||
|
не является условием завершения текущей задачи.
|
||||||
|
|
||||||
|
## Persist decisions и ratchet
|
||||||
|
|
||||||
|
Устойчивый pattern должен находиться через код и manifest. Добавляй convention,
|
||||||
|
только если следующей сессии всё ещё нужно явное project decision rule.
|
||||||
|
У каждого правила один owner; AGENTS содержит trigger и ссылку.
|
||||||
|
Устройство root tooling находится в README и конфигурации; их чтение не
|
||||||
|
требует соседних checkout. Audit/backlog не помещай в conventions.
|
||||||
|
|
||||||
|
Legacy фиксируется отдельно; новый diff не добавляет нарушений. При migration
|
||||||
|
закрывай подтверждённые baseline findings с evidence, сохраняя область проверки.
|
||||||
|
Зелёный lint не доказывает отсутствие semantic debt. Работа с baseline начинается
|
||||||
|
с [migration plan](../migrations/conventions-v1/plan.md), только когда задача
|
||||||
|
затрагивает migration или найденный там owner.
|
||||||
|
|
||||||
|
## Domain values
|
||||||
|
|
||||||
|
Finite business vocabulary (status, error code, operation kind, role) принадлежит
|
||||||
|
domain/module/entity owner. Перед определением найди существующие types/values;
|
||||||
|
в comparisons используй символ владельца, без конкурирующих словарей.
|
||||||
|
Для нового project-owned vocabulary без аналога: `as const` + derived union.
|
||||||
|
Сохраняй wire values; generated/external enums не переписывай.
|
||||||
|
|
||||||
|
Numeric policy называй по смыслу и единицам (`TIMEOUT_MS`, `PAGE_SIZE`).
|
||||||
|
Индексы, `index + 1`, `Math.max(value, 0)` и обычные labels/log messages
|
||||||
|
не выноси в constants без semantic benefit. Локальный lint/i18n profile всё равно
|
||||||
|
применим. Не создавай tests существования констант или custom magic-string analyzer.
|
||||||
|
Правила приложений принадлежат их локальным conventions.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Dependencies
|
||||||
|
|
||||||
|
## Перед добавлением
|
||||||
|
|
||||||
|
1. Найди аналог по назначению и реализации в affected repo.
|
||||||
|
2. Проверь его direct dependencies, текущий stack и native/stdlib API.
|
||||||
|
3. Определи масштаб: один простой случай или повторяющаяся техническая задача.
|
||||||
|
4. Используй существующее решение, если оно полностью покрывает поведение.
|
||||||
|
Новый пакет оправдан меньшей общей сложностью, а не сокращением одной строки.
|
||||||
|
|
||||||
|
Повтор non-business задачи в примерно 3+ независимых местах — сигнал пересмотреть
|
||||||
|
pattern, а не требование немедленно извлечь abstraction. Сравни общий helper,
|
||||||
|
native API и зрелую небольшую dependency: parsing, serialization, dates,
|
||||||
|
debounce, class composition. Три `Array.map` не требуют utility library.
|
||||||
|
Не продолжай копировать boilerplate, если один принятый способ дешевле сопровождать.
|
||||||
|
|
||||||
|
## При выборе пакета
|
||||||
|
|
||||||
|
- Укажи конкретную потребность, альтернативу из текущего stack и существенную
|
||||||
|
цену: совместимость, browser bundle/server runtime, maintenance/security.
|
||||||
|
- Импортируемый пакет объявляй напрямую в repo владельца; наличие в соседнем
|
||||||
|
repo или транзитивном дереве не является dependency contract.
|
||||||
|
- Сохраняй exact versions и lockfile. Перед Node-командами активируй `.nvmrc`
|
||||||
|
через `nvm use --silent`; воспроизводимая установка — `npm ci` в нужном repo.
|
||||||
|
- Peer-конфликты решай совместимыми версиями, без `--force`/`--legacy-peer-deps`.
|
||||||
|
Версии и compatibility принадлежат местному manifest/lockfile.
|
||||||
|
- После изменения запускай relevant install/type/build/behavior checks и
|
||||||
|
`npm audit` при изменении зависимостей. Не добавляй второй analyzer, когда задача решается
|
||||||
|
настройкой существующего; [карта проверок](guardrails.md).
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Git в workspace
|
||||||
|
|
||||||
|
- Выполняй Git-команды из репозитория владельца: root, frontend, backend,
|
||||||
|
docs и specs имеют независимые history, index и branches.
|
||||||
|
- Новая работа: ветка `codex/<task>` от актуального `develop`, затем PR в `develop`.
|
||||||
|
Сохраняй чужие staged/unstaged/untracked изменения; не переноси их в свой commit.
|
||||||
|
- Commit содержит одну связанную задачу и её необходимые проверки/документацию.
|
||||||
|
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
|
||||||
|
- Commits и заголовки PR — Conventional Commits; допустим `spec:`.
|
||||||
|
Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
|
||||||
|
- Перед commit проверь `git diff --cached --name-status` и сам staged diff
|
||||||
|
в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore):
|
||||||
|
новый общий файл получает точечный allowlist; проверь staged paths вручную. `git add -f` не используется.
|
||||||
|
- Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или
|
||||||
|
перенос опубликованных тегов как обычную очистку. Для опасной операции нужны
|
||||||
|
явная необходимость, сохранение данных и соответствующая авторизация.
|
||||||
|
- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop,
|
||||||
|
release-конфигурация требуют чтения
|
||||||
|
[процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy,
|
||||||
|
версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Root проверки
|
||||||
|
|
||||||
|
Root CI содержит только `commits`: штатный commitlint проверяет заголовок PR
|
||||||
|
и коммиты между BASE и HEAD. Установка — `npm ci --ignore-scripts`, Node — из
|
||||||
|
местной `.nvmrc`. Самописные проверки и дополнительные CI workflow не добавляются.
|
||||||
|
|
||||||
|
Локально перед Node-командами выполни `nvm use --silent`. Для изменений проверь
|
||||||
|
JSON/config syntax, тексты, ссылки и `git diff --check`; новые файлы просмотри
|
||||||
|
отдельно. Перед commit проверь staged paths относительно allowlist `.gitignore`.
|
||||||
|
Зависимости устанавливай через `npm ci`; при их изменении выполни `npm audit`.
|
||||||
|
|
||||||
|
Release-it использует встроенные проверки ветки, чистого дерева и наличия
|
||||||
|
коммитов. Перед подготовкой релиза вручную проверь теги и соответствие версии
|
||||||
|
последнему принятому тегу; повторно не повышай уже подготовленную версию.
|
||||||
|
Процесс — в [README](../../README.md#релизы-и-ветки).
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Test Intent
|
||||||
|
|
||||||
|
Перед нетривиальным тестом сформулируй наблюдаемый результат и риск регрессии
|
||||||
|
в описании задачи/PR, рядом с тестом либо в рабочем отчёте. Отдельный artifact
|
||||||
|
не обязателен. Для trivial low-risk изменения отдельный Test Intent не нужен.
|
||||||
|
|
||||||
|
Краткая форма, только применимые пункты:
|
||||||
|
|
||||||
|
- **Поведение:** что увидит пользователь/caller и какую ошибку должен поймать тест.
|
||||||
|
- **Варианты и границы:** значимые альтернативы, edge/error states, concurrency.
|
||||||
|
- **Исключено:** какие детали реализации не являются контрактом.
|
||||||
|
- **Уровень:** самая дешёвая boundary, которая надёжно различает правильный
|
||||||
|
и сломанный исход; критерии — в local testing convention.
|
||||||
|
|
||||||
|
Пример: «Два refresh с одним token: один успешный ответ, второй 401;
|
||||||
|
новая session остаётся рабочей. Проверить на реальной изолированной SQLite.
|
||||||
|
Не проверять порядок private calls». Mock race не доказывает DB atomicity.
|
||||||
|
|
||||||
|
Для каждого теста ответь: «Какой regression он обнаружит?» Не создавай
|
||||||
|
`Foo.test.tsx` из-за появления `Foo.tsx`. По умолчанию не тестируй obvious JSX,
|
||||||
|
CSS classes, каждый wrapper/aria attribute, decorators, trivial CRUD wiring,
|
||||||
|
framework и сторонние библиотеки. Проверяй доступность через пользовательское
|
||||||
|
действие/смысл, когда это риск; декларативный атрибут сам по себе не test intent.
|
||||||
|
|
||||||
|
Bug fix сначала воспроизводит исход на responsible boundary. После подходящих
|
||||||
|
проверок остановись: количество тестов и coverage ради coverage не являются целью.
|
||||||
|
Новые harness/framework/fixtures не нужны, когда существующий setup покрывает риск.
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
# Conventions v1: semantic audit
|
||||||
|
|
||||||
|
Дата: 2026-09-15. Read-only audit production code; conventions изменены отдельно.
|
||||||
|
Snapshot: root `2567a755569e6ef6ba12063b62f202fe52884ce7`, frontend
|
||||||
|
`0244acf429c525b23117f165bbe52ef5403eefd3`, backend
|
||||||
|
`660b250ecd05e38784690b9a9adb381764c0ca7c`. На старте worktrees чистые.
|
||||||
|
|
||||||
|
## Scope и метод
|
||||||
|
|
||||||
|
Прочитаны existing AGENTS/.ai, manifests, TypeScript/Biome/ESLint, dependency
|
||||||
|
rules, test/Storybook setup и OpenAPI flow. Проверены representative paths:
|
||||||
|
auth/refresh, bank cards/operations/summary, portfolio/income page VM, account
|
||||||
|
errors, instrument search и T-Bank/MOEX/NCR boundaries. `rg` использован для
|
||||||
|
callers, HTTP/Prisma/domain imports, raw fetch/SQL, logging, error strings и
|
||||||
|
state patterns. Точечная executable reproduction проверяет ValidationPipe → filter.
|
||||||
|
|
||||||
|
Это reviewed inventory подтверждённых findings, не полный автоматический
|
||||||
|
счётчик всех файлов. P0 — явное архитектурное нарушение / высокий риск;
|
||||||
|
P1 — неверная ответственность / существенная inconsistency;
|
||||||
|
P2 — cleanup / naming / менее важная consistency issue.
|
||||||
|
Confidence: High / Medium / Low. P0 по проверенной области не выявлены;
|
||||||
|
это не security certification.
|
||||||
|
|
||||||
|
| Severity | Findings |
|
||||||
|
| --- | --- |
|
||||||
|
| P0 | 0 |
|
||||||
|
| P1 | 4 |
|
||||||
|
| P2 | 3 |
|
||||||
|
|
||||||
|
## BE-001 — Validation теряет ошибки полей
|
||||||
|
|
||||||
|
- **Location:** [configureApp](../../../backend/src/main.ts), строка 28;
|
||||||
|
[HttpExceptionFilter](../../../backend/src/common/filters/http-exception.filter.ts),
|
||||||
|
`catch` / `validationErrors`.
|
||||||
|
- **Convention:** [Errors](../../../backend/.ai/conventions/error-handling.md),
|
||||||
|
[требование](../../../specs/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках).
|
||||||
|
- **Current state:** стандартный ValidationPipe формирует `message: string[]`;
|
||||||
|
filter трактует его как `ValidationError[]`, возвращая пустой `errors`.
|
||||||
|
- **Expected state:** invalid request → 400 / VALIDATION_ERROR с применимыми
|
||||||
|
`{ pointer, detail }`; вложенные поля обрабатываются согласно контракту.
|
||||||
|
- **Severity:** P1. **Confidence:** High.
|
||||||
|
- **Reason:** публичный контракт теряет field details; типовой filter unit test
|
||||||
|
и OpenAPI schema parity не проходят реальную validation pipeline.
|
||||||
|
- **Suggested migration:** отдельный behavior fix в Auth/API validation slice:
|
||||||
|
согласовать output pipe с filter; доказать через configured HTTP request.
|
||||||
|
Не добавлять новый validation framework.
|
||||||
|
|
||||||
|
## BE-002 — Repository решает business eligibility и HTTP outcome
|
||||||
|
|
||||||
|
- **Location:** [BankCardsRepository](../../../backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts),
|
||||||
|
`card(active)` и `write`; callers в
|
||||||
|
[BankOperationsService](../../../backend/src/modules/banking/bank-cards/application/bank-operations.service.ts).
|
||||||
|
- **Convention:** [Data](../../../backend/.ai/conventions/data.md),
|
||||||
|
[application boundaries](../../../backend/.ai/architecture.md#ответственность-и-направление-зависимостей).
|
||||||
|
- **Current state:** repository импортирует application `conflict/missing`,
|
||||||
|
решает запрет archived card и переводит общий P2002 в DuplicateCategory.
|
||||||
|
- **Expected state:** repository владеет owner-filtered reads, transaction/retry
|
||||||
|
и техническим DB outcome; application/domain определяет eligibility/public error.
|
||||||
|
- **Severity:** P1. **Confidence:** High.
|
||||||
|
- **Reason:** storage helper знает business policy нескольких callers;
|
||||||
|
P2002 не всегда означает duplicate category. Утечка доступа не утверждается.
|
||||||
|
- **Suggested migration:** Cards/Operations slice: перенести policy и mapping
|
||||||
|
к ответственным сценариям, сохранив lookup/check/write внутри той же transaction,
|
||||||
|
retry budget, idempotency, CAS и действующие public codes.
|
||||||
|
|
||||||
|
## FE-001 — Самостоятельные банковские действия находятся в entity UI
|
||||||
|
|
||||||
|
- **Location:** [BankCardForm](../../../frontend/src/entities/bank-card/ui/BankCardForm.tsx),
|
||||||
|
[CategoryManager](../../../frontend/src/entities/bank-card/ui/CategoryManager.tsx);
|
||||||
|
consumers — CardsPage и CardPage.
|
||||||
|
- **Convention:** [FSD ownership](../../../frontend/.ai/architecture.md#выбор-владельца).
|
||||||
|
- **Current state:** entity UI координирует создание/редактирование карты,
|
||||||
|
idempotency intent, submit, version adoption, создание/редактирование категорий.
|
||||||
|
- **Expected state:** самостоятельные пользовательские workflows имеют feature
|
||||||
|
owner; entity сохраняет модели, queries и presentation понятия карты/категории.
|
||||||
|
- **Severity:** P1. **Confidence:** High.
|
||||||
|
- **Reason:** это полноценные reused capabilities с собственным lifecycle,
|
||||||
|
а не вывод о слое из наличия одной кнопки или mutation.
|
||||||
|
- **Suggested migration:** по одному действию в Cards slice; presentation fields
|
||||||
|
сохранять/reuse, не перемещать всю entity и все её hooks механически.
|
||||||
|
|
||||||
|
## FE-002 — Views банковских страниц владеют query state selection
|
||||||
|
|
||||||
|
- **Location:** [CardPage.view](../../../frontend/src/pages/card/ui/CardPage.view.tsx),
|
||||||
|
[CardsPage.view](../../../frontend/src/pages/cards/ui/CardsPage.view.tsx),
|
||||||
|
соответствующие `CardPage.vm.ts` / `CardsPage.vm.ts` рядом.
|
||||||
|
- **Convention:** [Container/VM/view](../../../frontend/.ai/conventions/ui.md#container-vm-и-view).
|
||||||
|
- **Current state:** VM отдаёт query objects; views выбирают initial/cached/error
|
||||||
|
states через `data/isLoading/isError/isPlaceholderData` и вызывают `refetch`.
|
||||||
|
- **Expected state:** VM выбирает presentation states и retry callbacks,
|
||||||
|
views отображают данные/действия. DOM scroll/focus остаются UI responsibility.
|
||||||
|
- **Severity:** P1. **Confidence:** High.
|
||||||
|
- **Reason:** presentation зависит от query lifecycle и принимает решения,
|
||||||
|
которые existing account-income pattern уже размещает в VM.
|
||||||
|
- **Suggested migration:** Cards slice, сначала один экран: перенести state
|
||||||
|
selection без изменения loading/cached/recovery, URL pagination и scroll anchor.
|
||||||
|
Не строить generic state machine для всех страниц.
|
||||||
|
|
||||||
|
## FE-003 — Application navigation находится в shared UI
|
||||||
|
|
||||||
|
- **Location:** [AppShell](../../../frontend/src/shared/ui/AppShell.tsx),
|
||||||
|
`items`, `useLocation`, `Outlet`, nav rendering; public export в shared/ui.
|
||||||
|
- **Convention:** [FSD и устройство](../../../frontend/.ai/architecture.md).
|
||||||
|
- **Current state:** shared owner содержит конкретные `/accounts`, `/cards`,
|
||||||
|
`/stocks`, `/bonds`, `/settings` и application navigation.
|
||||||
|
- **Expected state:** navigation/composition принадлежит app; shared сохраняет
|
||||||
|
только действительно общие presentation primitives при текущей потребности.
|
||||||
|
- **Severity:** P2. **Confidence:** High.
|
||||||
|
- **Reason:** известное legacy placement уже отмечалось старым convention;
|
||||||
|
dependency graph не доказывает отсутствие business meaning в shared.
|
||||||
|
- **Suggested migration:** отдельный navigation slice вместе с app callers и CSS,
|
||||||
|
без новой оболочки над Mantine и без переразметки пользовательского UI.
|
||||||
|
|
||||||
|
## FE-004 — Connection recovery классифицируется повторно
|
||||||
|
|
||||||
|
- **Location:** [AccountPageError](../../../frontend/src/pages/account/ui/components/AccountPageError.view.tsx),
|
||||||
|
[AccountsPageError](../../../frontend/src/pages/accounts/ui/components/AccountsPageError.view.tsx),
|
||||||
|
[StocksPage.view](../../../frontend/src/pages/stocks/ui/StocksPage.view.tsx),
|
||||||
|
[BondsPage.view](../../../frontend/src/pages/bonds/ui/BondsPage.view.tsx).
|
||||||
|
- **Convention:** [Frontend errors](../../../frontend/.ai/conventions/data.md#errors),
|
||||||
|
[pattern convergence](../../conventions/code-health.md#search--reuse--converge).
|
||||||
|
- **Current state:** несколько owners повторяют `TBANK_NOT_CONNECTED` /
|
||||||
|
`INVALID_TBANK_TOKEN` comparisons; presentation/retry actions различаются.
|
||||||
|
- **Expected state:** общая классификация принадлежит одному допустимому owner,
|
||||||
|
локальный текст/actions остаются у сценария; unknown code сохраняет fallback.
|
||||||
|
- **Severity:** P2. **Confidence:** High.
|
||||||
|
- **Reason:** повторяется техническое decision rule. Различный UI сам по себе
|
||||||
|
не нарушение; generated `code: string` не разрешает выдумывать закрытый enum.
|
||||||
|
- **Suggested migration:** Accounts/Portfolio, затем Instruments/Settings:
|
||||||
|
унифицировать только classification с сохранением всех текущих recovery paths.
|
||||||
|
|
||||||
|
## BE-003 — Invalid-token protocol основан на Error.message
|
||||||
|
|
||||||
|
- **Location:** [TbankClient](../../../backend/src/integrations/tbank/tbank.client.ts),
|
||||||
|
`call` (throw Error); [TbankTokenService](../../../backend/src/integrations/tbank/tbank-token.service.ts),
|
||||||
|
`translateInvalidToken`; [InstrumentMetadataService](../../../backend/src/integrations/tbank/instrument-metadata.service.ts),
|
||||||
|
invalid-token catch.
|
||||||
|
- **Convention:** [Errors](../../../backend/.ai/conventions/error-handling.md),
|
||||||
|
[Integrations](../../../backend/.ai/conventions/integrations.md).
|
||||||
|
- **Current state:** low-level client бросает `Error('INVALID_TBANK_TOKEN')`,
|
||||||
|
два consumers сравнивают строку сообщения для выбора recovery/HTTP mapping.
|
||||||
|
- **Expected state:** один явный local typed/code outcome на integration boundary,
|
||||||
|
public HTTP translation остаётся у существующего owner.
|
||||||
|
- **Severity:** P2. **Confidence:** High.
|
||||||
|
- **Reason:** изменение диагностического текста ломает machine behavior;
|
||||||
|
raw provider exception сейчас уже скрыт, подтверждённой утечки нет.
|
||||||
|
- **Suggested migration:** Instruments/Settings integration slice; заменить
|
||||||
|
marker и всех consumers одной связной правкой, сохранив auth/unavailable
|
||||||
|
distinction и wire codes. Общий enterprise error framework не нужен.
|
||||||
|
|
||||||
|
## Что не признано нарушением
|
||||||
|
|
||||||
|
- Page-local components account/income/events и их flat views допустимы.
|
||||||
|
Размер файла, `map` или handler сами по себе не определяют feature/entity.
|
||||||
|
- Прямые Prisma queries связных auth/banking/connection services допускаются
|
||||||
|
existing data convention; standalone use cases уже используют repository.
|
||||||
|
- URL-applied values и editable draft dates — разные состояния; синхронизация
|
||||||
|
draft после navigation не равна лишнему server-state cache.
|
||||||
|
- Повторы `filter(Boolean).join(" · ")` формируют display metadata, а не CSS
|
||||||
|
classes; это не основание устанавливать `clsx`.
|
||||||
|
- `ProblemDetailsDto.code: string` и typed wire literals — ограничение принятого
|
||||||
|
генератора, не основание создавать ручную копию backend enum.
|
||||||
|
- Stories показывают synthetic presentation; отсутствие story/test у каждого
|
||||||
|
component не считается finding. По выполненным поискам не найдены raw fetch
|
||||||
|
вне shared/api, production console или raw SQL; это не доказательство полноты.
|
||||||
|
|
||||||
|
## Evidence BE-001
|
||||||
|
|
||||||
|
Из backend после `nvm use --silent`: одноразовый `node -r ts-node/register`
|
||||||
|
загрузил actual CredentialsDto, ValidationPipe и HttpExceptionFilter.
|
||||||
|
Input `{ email: 'bad', password: 'x' }` → exception `message: string[]` →
|
||||||
|
`{ status: 400, code: 'VALIDATION_ERROR', errors: [] }` (assert PASS).
|
||||||
|
DB/порты/внешняя сеть не использовались. Это proof пары pipe/filter;
|
||||||
|
исправление всё равно требует configured HTTP regression test.
|
||||||
|
|
||||||
|
Immutable baseline этого анализа; статусы закрытия и evidence будущих работ
|
||||||
|
вести в [plan](plan.md), не переписывая snapshot под новый код.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Conventions v1: migration plan
|
||||||
|
|
||||||
|
Исходный [audit](audit.md): 7 открытых findings — P0: 0, P1: 4, P2: 3.
|
||||||
|
Это число reviewed findings, а не все violations репозитория. Production
|
||||||
|
refactoring в задаче conventions не выполнялся. Порядок ниже — рекомендуемые
|
||||||
|
отдельные работы, без обязательного запуска SDD или repository-wide cleanup.
|
||||||
|
|
||||||
|
## Vertical slices
|
||||||
|
|
||||||
|
| Slice / порядок | Findings и target ownership | Сохранить / Test Intent | Migration и verification |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1. Auth: invalid request | BE-001; configureApp validation + HTTP filter | 400/code, field pointers/details, safe unknown errors, whitelist semantics; invalid request не вызывает use case | Отдельный behavior fix и configured HTTP regression, без структурного рефакторинга. Auth e2e, filter tests, contract check, backend verify |
|
||||||
|
| 2. Cards: создание/редактирование | FE-001; feature workflows + entity presentation/queries | Submit blocking, ввод после failure, idempotency ID, version conflict/adoption, archived editing | Сначала одно действие, затем следующее. Только structural change. Existing banking/form RTL/MSW, relevant stories, deps/knip и frontend verify |
|
||||||
|
| 3. Cards/Operations: просмотр и запись | FE-002 + BE-002; page VM states; application policy; repository transaction | Cached snapshot/refetch failure, URL period/page, scroll anchor; user isolation, CAS, atomic transfer/refund, balances | Раздельные frontend/backend commits; не менять behavior вместе с переносом. RTL/MSW и browser pagination; banking unit + SQLite/HTTP, relevant durability, verify обоих repos |
|
||||||
|
| 4. Accounts/Portfolio: recovery | FE-004, первая группа consumers; общий classifier у допустимого owner | NOT_FOUND → accounts, connection → settings, transient error → retry; сохранить различия текста/actions и fallback unknown code | Structural convergence одного decision rule. Account/portfolio RTL/MSW и frontend verify; оставшиеся instrument consumers учитываются до полного закрытия FE-004 |
|
||||||
|
| 5. Instruments/Settings: connection | FE-004 оставшиеся consumers + BE-003; integration marker/translation | Invalid token и unavailable различаются; bounded retry, cache isolation/generation, metadata fallback | Сначала marker и все backend callers, отдельно frontend classifier reuse. T-Bank/token/metadata tests, relevant API e2e, frontend recovery tests, verify affected repos |
|
||||||
|
| 6. Navigation между областями | FE-003; app composition | Active links desktop/mobile, nested Outlet, breadcrumbs, auth fallback, keyboard и narrow viewport | Перенос existing AppShell с CSS/callers, без нового wrapper/UI redesign. Route tests, deps/knip, browser proof и frontend verify |
|
||||||
|
|
||||||
|
Каждый slice начинает с классификации responsibility и просмотра всех callers.
|
||||||
|
До edits зафиксируй target owner, сохраняемое behavior и применимый
|
||||||
|
[Test Intent](../../conventions/testing.md). Structural refactor и behavior
|
||||||
|
change идут отдельными шагами/commits; завершение требует semantic review,
|
||||||
|
relevant checks и обновления статуса finding с evidence.
|
||||||
|
|
||||||
|
## Ratchet
|
||||||
|
|
||||||
|
Все семь IDs пока **Open**. Следующая migration фиксирует здесь:
|
||||||
|
`ID → Open/Closed → repo commit/PR → команда и результат`.
|
||||||
|
Закрывай finding только после миграции всех перечисленных consumers; частичный
|
||||||
|
перенос не уменьшает count. Новую находку добавляй отдельным ID с severity,
|
||||||
|
confidence и scope; изменение охвата не выдавай за улучшение качества.
|
||||||
|
|
||||||
|
Цель — последовательно уменьшать open baseline и не добавлять новых нарушений
|
||||||
|
в diff. Existing exceptions остаются видимыми и не становятся образцами нового
|
||||||
|
кода. Исходный audit сохраняет facts своего snapshot.
|
||||||
|
|
||||||
|
## Deterministic follow-ups
|
||||||
|
|
||||||
|
Полный mapping — в [guardrails](../../conventions/guardrails.md).
|
||||||
|
Existing dependency-cruiser поддерживает `--ignore-known` (подтверждено local
|
||||||
|
CLI help). При отдельной работе над same-layer imports сначала проверить
|
||||||
|
candidate rule; если существующих violations нет, baseline не нужен.
|
||||||
|
При legacy использовать native exact baseline, без directory-wide exclusions.
|
||||||
|
|
||||||
|
Backend import boundaries и `no-console` при повторном regression можно
|
||||||
|
закрепить встроенными ESLint rules. Markdown `.ai` можно включить явными путями
|
||||||
|
в existing markdownlint workflow, когда согласована доступность соседних
|
||||||
|
checkout; сегодня проверка `.ai` выполнена локально тем же инструментом.
|
||||||
|
Новые analyzers, scripts, dependencies или custom baseline infrastructure
|
||||||
|
в этом изменении не создаются.
|
||||||
|
|
||||||
|
## Verification conventions v1
|
||||||
|
|
||||||
|
Проверено 2026-09-15 на согласованном workspace из трёх веток
|
||||||
|
`codex/engineering-conventions`; docs/specs остались без изменений.
|
||||||
|
|
||||||
|
| Проверка | Результат |
|
||||||
|
| --- | --- |
|
||||||
|
| Root `python3 test-workspace-tracking.py` | PASS: exact `.ai` allowlist, соседние repos и вымышленные local/private files сохранены |
|
||||||
|
| Из docs: `npm exec -- markdownlint-cli2 --config .markdownlint-cli2.jsonc '../AGENTS.md' '../README.md' '../.ai/**/*.md' '../frontend/AGENTS.md' '../frontend/.ai/**/*.md' '../backend/AGENTS.md' '../backend/.ai/**/*.md'` | PASS: 31 Markdown, включая новые файлы, 0 ошибок style/relative links/anchors |
|
||||||
|
| Docs `npm run verify` | PASS: 42 Markdown, 0 ошибок; проверены также входящие межрепозиторные ссылки |
|
||||||
|
| Frontend `npm run deps`, `npm run deps:storybook` | PASS: 297/298 modules, 0 нарушений настроенных dependency rules |
|
||||||
|
| Backend ValidationPipe → filter reproduction | Подтверждён BE-001: invalid CredentialsDto → 400/VALIDATION_ERROR, `errors: []` |
|
||||||
|
| `git diff --check` в root/frontend/backend/docs/specs | PASS |
|
||||||
|
|
||||||
|
Semantic review первого прохода: маршруты UI-only, API/contract, auth, tests-only, helper/package,
|
||||||
|
Prisma, logging и migration достигают нужных owners; ссылки compatibility
|
||||||
|
не дублируют таблицы routing. Общие policy имеют root owner, stack-specific
|
||||||
|
исключения остались локальными. Проверены новые файлы и отсутствие production,
|
||||||
|
manifest/lockfile, generated code, CI/release и docs/specs изменений.
|
||||||
|
|
||||||
|
Application build/coverage/e2e целиком не запускались: изменение Markdown и
|
||||||
|
root tracking не меняет production behavior. Existing dependency checks и
|
||||||
|
воспроизведение BE-001 подтверждают только указанные границы, не отсутствие
|
||||||
|
всех semantic/security проблем. Full gates выполняются при будущих code slices.
|
||||||
|
|
||||||
|
## Упрощение структуры после review
|
||||||
|
|
||||||
|
Conventions frontend/backend именуются без repo-префиксов. Устройство и правила
|
||||||
|
архитектуры объединены в единственный `.ai/architecture.md` каждого repo.
|
||||||
|
Routing и карта источников объединены в `AGENTS.md`; прежние CONVENTIONS/SOURCES
|
||||||
|
и отдельные architecture conventions удалены без compatibility-заглушек.
|
||||||
|
Активные ссылки обновлены; в audit изменены только ссылки, findings сохранены.
|
||||||
|
Исторические архивы specs остаются источниками своего времени.
|
||||||
|
|
||||||
|
Повторная проверка: existing markdownlint-cli2 с docs config по root/frontend/backend
|
||||||
|
AGENTS, README и `.ai/**/*.md` — 27 файлов, 0 ошибок, включая относительные ссылки
|
||||||
|
и якоря. `git diff --check` — PASS. Production-код и dependencies не менялись.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Copy to a restricted, untracked file. Never commit filled values.
|
||||||
|
# Full release image references; use published version tags, not latest.
|
||||||
|
FRONTEND_IMAGE=nexus.ksv741.keenetic.pro/wealthledger/frontend:0.2.0
|
||||||
|
BACKEND_IMAGE=nexus.ksv741.keenetic.pro/wealthledger/backend:0.2.0
|
||||||
|
FRONTEND_PORT=8080
|
||||||
|
FRONTEND_ORIGIN=https://wealthledger.example.com
|
||||||
|
# Absolute directory, pre-created with owner UID/GID 1000:1000 and mode 0700.
|
||||||
|
DATA_DIRECTORY=/srv/wealthledger/data
|
||||||
|
# Generate once and keep across updates. Store an independent key backup.
|
||||||
|
JWT_SECRET=
|
||||||
|
TOKEN_ENCRYPTION_KEY=
|
||||||
|
LOG_LEVEL=info
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Local-only fixture values. Never use this file for a server deployment.
|
||||||
|
# Base-file interpolation happens before overrides; these references are replaced.
|
||||||
|
FRONTEND_IMAGE=wealthledger-frontend:development
|
||||||
|
BACKEND_IMAGE=wealthledger-backend:development
|
||||||
|
DATA_DIRECTORY=/unused-development-volume
|
||||||
|
FRONTEND_PORT=5173
|
||||||
|
FRONTEND_ORIGIN=http://localhost:5173
|
||||||
|
JWT_SECRET=local-development-only-not-a-server-secret
|
||||||
|
TOKEN_ENCRYPTION_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
|
||||||
|
LOG_LEVEL=info
|
||||||
@@ -22,5 +22,5 @@ jobs:
|
|||||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
run: |
|
run: |
|
||||||
printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict
|
printf '%s\n' "$PR_TITLE" | npm exec --no -- commitlint --strict
|
||||||
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
name: Workspace security
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
pull_request:
|
|
||||||
schedule:
|
|
||||||
- cron: '17 5 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
security:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: https://github.com/actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Record checked revision
|
|
||||||
run: git rev-parse HEAD
|
|
||||||
- name: Scan history with Gitleaks 8.30.1
|
|
||||||
run: |
|
|
||||||
curl --fail --location --silent --show-error https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz -o /tmp/gitleaks.tar.gz
|
|
||||||
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' | sha256sum --check
|
|
||||||
mkdir -p /tmp/gitleaks-bin
|
|
||||||
tar -xzf /tmp/gitleaks.tar.gz -C /tmp/gitleaks-bin gitleaks
|
|
||||||
/tmp/gitleaks-bin/gitleaks git --log-opts=--all --redact=100 --no-banner
|
|
||||||
- uses: https://github.com/actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
- name: Audit runtime and development dependencies
|
|
||||||
run: npm audit --include=dev --audit-level=low
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
name: Workspace verify
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ['**']
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: https://github.com/actions/checkout@v4
|
|
||||||
- uses: https://github.com/actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run verify
|
|
||||||
+20
-7
@@ -3,22 +3,35 @@
|
|||||||
!/.gitignore
|
!/.gitignore
|
||||||
!/AGENTS.md
|
!/AGENTS.md
|
||||||
!/README.md
|
!/README.md
|
||||||
!/test-workspace-tracking.py
|
!/compose.yaml
|
||||||
|
!/compose.development.yaml
|
||||||
|
!/.env.compose.example
|
||||||
|
!/.env.development.example
|
||||||
|
!/.ai/
|
||||||
|
/.ai/*
|
||||||
|
!/.ai/conventions/
|
||||||
|
/.ai/conventions/*
|
||||||
|
!/.ai/conventions/git.md
|
||||||
|
!/.ai/conventions/dependencies.md
|
||||||
|
!/.ai/conventions/code-health.md
|
||||||
|
!/.ai/conventions/testing.md
|
||||||
|
!/.ai/conventions/guardrails.md
|
||||||
|
!/.ai/migrations/
|
||||||
|
/.ai/migrations/*
|
||||||
|
!/.ai/migrations/conventions-v1/
|
||||||
|
/.ai/migrations/conventions-v1/*
|
||||||
|
!/.ai/migrations/conventions-v1/audit.md
|
||||||
|
!/.ai/migrations/conventions-v1/plan.md
|
||||||
!/.nvmrc
|
!/.nvmrc
|
||||||
!/package.json
|
!/package.json
|
||||||
!/package-lock.json
|
!/package-lock.json
|
||||||
!/.release-it.json
|
!/.release-it.json
|
||||||
!/CHANGELOG.md
|
!/CHANGELOG.md
|
||||||
!/commitlint.config.cjs
|
!/commitlint.config.cjs
|
||||||
|
!/.gitleaksignore
|
||||||
!/renovate.json
|
!/renovate.json
|
||||||
!/test-release.cjs
|
|
||||||
!/scripts/
|
|
||||||
/scripts/*
|
|
||||||
!/scripts/check-release.cjs
|
|
||||||
!/.gitea/
|
!/.gitea/
|
||||||
/.gitea/*
|
/.gitea/*
|
||||||
!/.gitea/workflows/
|
!/.gitea/workflows/
|
||||||
/.gitea/workflows/*
|
/.gitea/workflows/*
|
||||||
!/.gitea/workflows/commits.yml
|
!/.gitea/workflows/commits.yml
|
||||||
!/.gitea/workflows/verify.yml
|
|
||||||
!/.gitea/workflows/security.yml
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Reviewed historical local-development fixture: base64 of the deterministic
|
||||||
|
# string 0123456789abcdef repeated twice, never a runtime secret.
|
||||||
|
7e382d460be296b6c88e17084fff17bb7728f6ea:.env.development.example:generic-api-key:9
|
||||||
@@ -18,9 +18,6 @@
|
|||||||
"gitlab": {
|
"gitlab": {
|
||||||
"release": false
|
"release": false
|
||||||
},
|
},
|
||||||
"hooks": {
|
|
||||||
"after:git:init": "node scripts/check-release.cjs"
|
|
||||||
},
|
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"@release-it/conventional-changelog": {
|
"@release-it/conventional-changelog": {
|
||||||
"infile": "CHANGELOG.md",
|
"infile": "CHANGELOG.md",
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
# WealthLedger
|
# WealthLedger workspace
|
||||||
|
|
||||||
## Workspace
|
Root содержит workspace-навигацию и конфигурацию стандартных инструментов.
|
||||||
|
Самописные скрипты не добавляются; CI выполняет только commitlint.
|
||||||
|
`frontend`, `backend`, `docs`, `specs` — самостоятельные Git-репозитории.
|
||||||
|
Root tracking ограничен allowlist; локальные данные и nested repos в него
|
||||||
|
не добавляются. `references/**`, dated audits и архивы — read-only.
|
||||||
|
|
||||||
`frontend`, `backend`, `docs` и `specs` — независимые Git-репозитории.
|
Инструкции root относятся только к root. Работа внутри другого репозитория
|
||||||
Root — отдельный Git-репозиторий `WealthLedger/main`; команды Git выполняй
|
начинается с его собственного AGENTS; он содержит все обязательные правила
|
||||||
из нужного repo. Root tracking ограничен allowlist в `.gitignore`;
|
без наследования root conventions и без чтения соседних checkout.
|
||||||
вложенные repos и локальные данные в main не добавляй, включая `git add -f`.
|
Публичные API artifacts передаются явно; CI каждого repo использует локальные
|
||||||
Структура checkout и запуск — в [README](README.md).
|
входы. Справочные источники не становятся обязательной цепочкой инструкций.
|
||||||
`references/**`, dated audits и архивы — read-only источники.
|
|
||||||
|
|
||||||
## Работа
|
Начни с результата и affected areas. До edits проследи поведение/invariants,
|
||||||
|
найди owner и 1–3 примера. Работай напрямую; OpenSpec/SDD — по явному запросу.
|
||||||
|
|
||||||
1. Начинай с задачи пользователя, ожидаемого результата и affected areas. Работай напрямую: OpenSpec/SDD artifacts, approvals и archive не являются prerequisite. Используй прежний SDD workflow только по явному запросу.
|
| Root задача | Прочитать |
|
||||||
2. Прочитай local `AGENTS.md` affected repo. Для frontend/backend прочитай его `.ai/CONVENTIONS.md` и `.ai/SOURCES.md`, затем только темы, которые активируют задача, пути или обнаруженная граница.
|
| --- | --- |
|
||||||
3. До выбора реализации проследи поведение и инварианты, найди ближайший существующий owner и 1–3 подходящих примера. Код показывает реализацию; документированный контракт не выводится из случайного аналога.
|
| Helper/pattern, refactor, scope | [Code health](.ai/conventions/code-health.md) |
|
||||||
4. Выбирай простейшее полное решение у ответственного владельца. Новая abstraction или dependency требует текущей потребности; посторонний рефакторинг оставляй отдельно.
|
| Dependency/tooling | [Dependencies](.ai/conventions/dependencies.md), [Guardrails](.ai/conventions/guardrails.md) |
|
||||||
5. Выбирай тип знания по задаче через [вход docs](docs/README.md): новая функция или межрепозиторное поведение → соответствующая область, спецификации, нужные ADR, затем реализация; ошибка с неясным ожиданием → спецификация, docs, код и тесты; архитектурное изменение → архитектура/ADR, затронутые спецификации и код. Локальная ошибка сборки, линтера или очевидный implementation bug → инструкции и код нужного repo; документация → docs/AGENTS.md и CONTRIBUTING.md. Расширяй контекст по конкретному пробелу; missing required source или конфликт требований сообщи до зависимого решения.
|
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) |
|
||||||
6. При новой границе и перед завершением повтори routing по actual staged, unstaged и новым файлам. Сохраняй чужие изменения; уточнение контекста не расширяет задачу.
|
| Git/branch/commit/PR/tracking | [Git](.ai/conventions/git.md) |
|
||||||
7. Проверь результат по задаче, рискованным случаям и local gates. Сохрани краткие команды/результаты; сообщи оставшиеся ограничения. Успех lint/tests не заменяет semantic review.
|
| Release/hotfix | [README](README.md#релизы-и-ветки) |
|
||||||
|
|
||||||
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`.
|
Перед Node-командой активируй местную `.nvmrc`: `nvm use --silent`.
|
||||||
|
При новой границе и перед завершением повтори routing по staged/unstaged/new
|
||||||
## Ветки и релизы
|
files; сохраняй чужие изменения. Missing source/конфликт требований сообщи
|
||||||
|
до зависимого решения. После diff выполни
|
||||||
Новая работа — в отдельной ветке от `develop`, затем PR в `develop`.
|
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review) и
|
||||||
Прямой push в `main` запрещён. Commits и заголовки PR — Conventional Commits.
|
[relevant gates](.ai/conventions/guardrails.md). Запиши краткие команды,
|
||||||
Перед release/hotfix, merge между `main` и `develop`, изменением release-конфигурации
|
результаты и ограничения; lint не заменяет semantic review.
|
||||||
или CI refs прочитай [процесс релиза](README.md#релизы-и-ветки).
|
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.2.1](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.0...v0.2.1) (2026-10-07)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** refresh release security baseline ([f27487b](https://git.personal.ksv741.ru/WealthLedger/main/commit/f27487b6a8cb59243a34e392059010dab38efd51))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* link production deployment repository ([9fbc23f](https://git.personal.ksv741.ru/WealthLedger/main/commit/9fbc23f7570ecb6ea13f7fae5490504d1a0d9ba8))
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
* point container images to Nexus [#15](https://git.personal.ksv741.ru/WealthLedger/main/issues/15) ([80fa254](https://git.personal.ksv741.ru/WealthLedger/main/commit/80fa254a31d525dbc98bb3fe2d949f78c8e5872b))
|
||||||
|
|
||||||
|
## [0.2.0](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.1.0...v0.2.0) (2026-09-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add containerized application delivery [#11](https://git.personal.ksv741.ru/WealthLedger/main/issues/11) ([7e382d4](https://git.personal.ksv741.ru/WealthLedger/main/commit/7e382d460be296b6c88e17084fff17bb7728f6ea))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* align inventory with simplified conventions ([4a78dd6](https://git.personal.ksv741.ru/WealthLedger/main/commit/4a78dd640d4a1db1b7c9dc4036375c076d55b34d))
|
||||||
|
* establish workspace engineering conventions ([93dd553](https://git.personal.ksv741.ru/WealthLedger/main/commit/93dd5539ad8bd10e11c59708e7b66affc19e4883))
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
* simplify root tooling and repository boundaries ([95e4361](https://git.personal.ksv741.ru/WealthLedger/main/commit/95e4361599244598126394aa658a0a27d77c0686))
|
||||||
|
|
||||||
## 0.1.0 (2026-09-14)
|
## 0.1.0 (2026-09-14)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Локальное приложение для портфеля T-Bank, операций, дохода и будущих выплат,
|
Локальное приложение для портфеля T-Bank, операций, дохода и будущих выплат,
|
||||||
а также ручного учёта банковских карт и расходов.
|
а также ручного учёта банковских карт и расходов.
|
||||||
[Обзор продукта](docs/overview.md) и [документация системы](docs/README.md).
|
[Обзор продукта](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/overview.md) и [документация системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md).
|
||||||
|
|
||||||
## Структура workspace
|
## Структура workspace
|
||||||
|
|
||||||
@@ -12,10 +12,10 @@ Workspace состоит из пяти независимых Git-репозит
|
|||||||
| Путь от root | Репозиторий Gitea | Назначение |
|
| Путь от root | Репозиторий Gitea | Назначение |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking |
|
| `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking |
|
||||||
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](frontend/README.md) |
|
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md) |
|
||||||
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](backend/README.md) |
|
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md) |
|
||||||
| `docs/` | `WealthLedger/docs` | [Объяснение системы](docs/README.md) |
|
| `docs/` | `WealthLedger/docs` | [Объяснение системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md) |
|
||||||
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](specs/README.md) |
|
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md) |
|
||||||
|
|
||||||
### Получение checkout
|
### Получение checkout
|
||||||
|
|
||||||
@@ -34,12 +34,14 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs
|
|||||||
Для существующего workspace клонируйте только отсутствующие checkout в пустые
|
Для существующего workspace клонируйте только отсутствующие checkout в пустые
|
||||||
каталоги. Проверяйте состояние каждого repo отдельно, например
|
каталоги. Проверяйте состояние каждого repo отдельно, например
|
||||||
`git -C frontend status`. Обновление main не обновляет соседние repos;
|
`git -C frontend status`. Обновление main не обновляет соседние repos;
|
||||||
общего lockfile revisions нет. Относительные ссылки требуют указанных имён каталогов.
|
общего lockfile revisions нет. Такая раскладка удобна для совместной работы,
|
||||||
|
но каждый checkout можно использовать отдельно.
|
||||||
|
|
||||||
### Что хранится в main
|
### Что хранится в main
|
||||||
|
|
||||||
Root отслеживает входные документы, npm/release-конфигурацию, changelog,
|
Root отслеживает входные документы, собственные engineering conventions и исторический migration
|
||||||
проверки tracking/release и три CI workflow. Точный allowlist — в `.gitignore`.
|
inventory в `.ai/`, npm/release-конфигурацию, changelog,
|
||||||
|
Compose configuration, безопасные env examples и один CI workflow с commitlint. Точный allowlist — в `.gitignore`.
|
||||||
Остальные root-пути игнорируются по умолчанию:
|
Остальные root-пути игнорируются по умолчанию:
|
||||||
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
|
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
|
||||||
`design/`, `todo/` и локальные настройки инструментов.
|
`design/`, `todo/` и локальные настройки инструментов.
|
||||||
@@ -51,6 +53,34 @@ Root отслеживает входные документы, npm/release-ко
|
|||||||
Не запускайте очистку workspace через `git clean -fdx`: ignored-пути содержат
|
Не запускайте очистку workspace через `git clean -fdx`: ignored-пути содержат
|
||||||
локальные данные. Правила tracking не удаляют файлы и вложенные `.git`.
|
локальные данные. Правила tracking не удаляют файлы и вложенные `.git`.
|
||||||
|
|
||||||
|
## Запуск через Docker Compose
|
||||||
|
|
||||||
|
Нужны Docker Engine и Compose >= 2.24.4, а для development — checkout
|
||||||
|
`frontend/` и `backend/` рядом с root. Компоненты собираются независимо:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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/` используют один origin,
|
||||||
|
Nest работает в watch mode. Host `node_modules` и `.env` не монтируются.
|
||||||
|
Данные development хранятся отдельно в named volume `development-data` проекта `wealthledger-dev`;
|
||||||
|
обычные restart/down их сохраняют. Изменение dependencies/config требует rebuild.
|
||||||
|
|
||||||
|
Production использует отдельный [репозиторий deploy](https://git.personal.ksv741.ru/WealthLedger/deploy):
|
||||||
|
его Git-тег закрепляет пару images, а `./deploy.sh vX.Y.Z` выполняет обновление.
|
||||||
|
Рабочие данные, сертификат T-Bank и постоянные ключи находятся вне checkout.
|
||||||
|
Только frontend публикует `127.0.0.1:8080`; серверный Caddy направляет туда весь
|
||||||
|
домен. Backend port остаётся внутренним. Корневой Compose сохраняется для development.
|
||||||
|
|
||||||
|
Пошаговая процедура установки, обновления и восстановления — README `deploy`;
|
||||||
|
архитектурные границы — [документация](https://git.personal.ksv741.ru/WealthLedger/docs),
|
||||||
|
страница `architecture/deployment.md`.
|
||||||
|
Перед backend update обязательны остановка, backup и успешная migration.
|
||||||
|
Image rollback не восстанавливает старую базу. Публикация component images
|
||||||
|
принадлежит их Gitea workflows; root CI остаётся только commitlint.
|
||||||
|
|
||||||
## Подготовка и запуск
|
## Подготовка и запуск
|
||||||
|
|
||||||
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
|
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
|
||||||
@@ -60,7 +90,7 @@ Root отслеживает входные документы, npm/release-ко
|
|||||||
После смены major Node переустановите native dependencies через `npm ci`.
|
После смены major Node переустановите native dependencies через `npm ci`.
|
||||||
Разрешения install scripts заданы в `package.json` соответствующего repo.
|
Разрешения install scripts заданы в `package.json` соответствующего repo.
|
||||||
|
|
||||||
Для нового backend создайте `.env` по [backend README](backend/README.md),
|
Для нового backend создайте `.env` по [backend README](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md),
|
||||||
замените оба placeholder-секрета и проверьте `DATABASE_URL`.
|
замените оба placeholder-секрета и проверьте `DATABASE_URL`.
|
||||||
Существующий `.env` сохраняйте. Из backend:
|
Существующий `.env` сохраняйте. Из backend:
|
||||||
|
|
||||||
@@ -73,13 +103,13 @@ npm run start:dev
|
|||||||
|
|
||||||
Build генерирует Prisma client. Перед миграцией существующей базы сохраните
|
Build генерирует Prisma client. Перед миграцией существующей базы сохраните
|
||||||
согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите
|
согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите
|
||||||
в [описании хранения](docs/architecture/containers.md#сохранение-и-восстановление-данных).
|
в [описании хранения](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/architecture/containers.md#сохранение-и-восстановление-данных).
|
||||||
Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в
|
Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в
|
||||||
`backend/prisma.config.ts`: CLI удаляет унаследованную переменную.
|
`backend/prisma.config.ts`: CLI удаляет унаследованную переменную.
|
||||||
Используйте штатный `prisma migrate deploy` из backend; прежний Prisma blocker
|
Используйте штатный `prisma migrate deploy` из backend; прежний Prisma blocker
|
||||||
снят. [Подробности и границы проверки](backend/README.md#реальная-sqlite-и-сохранность-данных).
|
снят. [Подробности и границы проверки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#реальная-sqlite-и-сохранность-данных).
|
||||||
|
|
||||||
В другом терминале подготовьте `.env` по [frontend README](frontend/README.md),
|
В другом терминале подготовьте `.env` по [frontend README](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md),
|
||||||
затем из frontend:
|
затем из frontend:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -98,7 +128,7 @@ npm run dev
|
|||||||
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
|
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
|
||||||
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
|
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
|
||||||
Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
||||||
[Работа с OpenSpec](specs/AGENTS.md), [команды validation](specs/README.md#проверка).
|
[Работа с OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AGENTS.md), [команды validation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md#проверка).
|
||||||
|
|
||||||
## Проверки
|
## Проверки
|
||||||
|
|
||||||
@@ -106,17 +136,18 @@ Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
|||||||
|
|
||||||
| Где | Команда | Что проверяет |
|
| Где | Команда | Что проверяет |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| root | `python3 test-workspace-tracking.py` | Массовый add на вымышленных данных; сохранность файлов и вложенных Git |
|
| root | `npm exec --no -- commitlint --strict --from <base> --to <head>` | Формат сообщений коммитов |
|
||||||
| root | `git diff --check` | Whitespace в изменениях root |
|
| root | `git diff --check` | Whitespace в изменениях root |
|
||||||
| frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки |
|
| frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки |
|
||||||
| backend | `npm run verify` | Lint, типы, unit tests и build |
|
| backend | `npm run verify` | Lint, типы, unit tests и build |
|
||||||
| backend | `npm run test:e2e` | HTTP/SQLite suites и контракт с соседним frontend |
|
| backend | `npm run test:e2e` | HTTP/SQLite suites и локальный OpenAPI artifact |
|
||||||
| docs | `npm run verify` | Markdown, относительные ссылки и diff |
|
| docs | `npm run verify` | Markdown, относительные ссылки и diff |
|
||||||
|
|
||||||
Python 3 нужен только для проверки root tracking. Backend e2e использует
|
Root CI выполняет только commitlint: проверяет сообщения коммитов и заголовок PR.
|
||||||
|
Allowlist root проверяется вручную по staged paths. Backend e2e использует
|
||||||
временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность
|
временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность
|
||||||
T-Bank. Полные команды и ограничения принадлежат README
|
T-Bank. Полные команды и ограничения принадлежат README
|
||||||
[frontend](frontend/README.md#проверка) и [backend](backend/README.md#проверка).
|
[frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md#проверка) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#проверка).
|
||||||
Для изменения проекта начните с [AGENTS](AGENTS.md).
|
Для изменения проекта начните с [AGENTS](AGENTS.md).
|
||||||
|
|
||||||
## Релизы и ветки
|
## Релизы и ветки
|
||||||
@@ -143,18 +174,19 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
|||||||
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
|
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
|
||||||
Проверить, что текущий `develop` содержит последний релиз из `main`.
|
Проверить, что текущий `develop` содержит последний релиз из `main`.
|
||||||
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
|
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
|
||||||
2. Для backend и docs обновить `.ci/refs.json`: полные SHA совместимых соседних
|
2. Проверить локальные входы и публичные artifacts выпускаемого репозитория.
|
||||||
репозиториев. Эти revisions должны быть доступны в Gitea. Проверить именно
|
Совместимость развёрнутых участников согласовать отдельно через публичные
|
||||||
их совместимость; движущиеся ветки не являются фиксацией состава релиза.
|
контракты; CI не скачивает соседние checkout и не требует их SHA.
|
||||||
Закоммитить обновлённые pins до подготовки версии.
|
|
||||||
3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем
|
3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем
|
||||||
`npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет
|
`npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет
|
||||||
`package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет.
|
`package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет.
|
||||||
Не использовать обход branch/clean/tag guards. Незакоммиченные файлы,
|
В root встроенные проверки release-it контролируют ветку, чистое дерево и
|
||||||
неподходящая ветка, отсутствие изменений и уже подготовленная версия блокируют выпуск.
|
наличие коммитов. Теги и соответствие версии последнему принятому тегу
|
||||||
|
проверяются вручную; уже подготовленную версию повторно не повышать.
|
||||||
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
|
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
|
||||||
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
|
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
|
||||||
`chore(release): X.Y.Z`. После зелёных `verify`, `security`, `commits`
|
`chore(release): X.Y.Z`. После зелёных обязательных проверок (`commits` в root;
|
||||||
|
`verify`, `security`, `commits` в остальных repo)
|
||||||
выполнить **fast-forward-only merge**. Если `main` изменился, сначала
|
выполнить **fast-forward-only merge**. Если `main` изменился, сначала
|
||||||
согласовать историю и повторить проверку кандидата; не обходить защиту.
|
согласовать историю и повторить проверку кандидата; не обходить защиту.
|
||||||
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
|
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
|
||||||
@@ -184,25 +216,26 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
|||||||
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
|
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
|
||||||
Если ломается публичный контракт, major сохраняется и для hotfix.
|
Если ломается публичный контракт, major сохраняется и для hotfix.
|
||||||
|
|
||||||
Backend CI проверяет runtime OpenAPI с frontend; docs CI — межрепозиторные
|
Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера,
|
||||||
ссылки. Для `main`, PR в `main` и `release/*` соседи берутся из `.ci/refs.json`;
|
сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному
|
||||||
для разработки — из `develop`. SHA записываются в логи. Для изменения сразу
|
snapshot. Docs проверяет внутренние ссылки; внешние источники даются как
|
||||||
нескольких репозиториев сначала согласовать их revisions, затем проверять
|
справочные веб-ссылки. Обновление API artifact потребителя — отдельное действие.
|
||||||
релизный набор; порядок публикации сам по себе не доказывает совместимость.
|
Зелёные локальные gates и порядок публикации не доказывают совместимость
|
||||||
|
развёрнутых версий. Процедуры выпуска находятся также в README каждого repo;
|
||||||
|
доступ к root для выпуска другого репозитория не требуется.
|
||||||
|
|
||||||
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
|
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
|
||||||
обязательные проверки `verify`, `security`, `commits` и запрет обхода правил
|
обязательные проверки: в root только `commits`, в остальных repo —
|
||||||
|
`verify`, `security`, `commits`; также нужен запрет обхода правил
|
||||||
администратором. Требование отдельного одобрения review не вводится для
|
администратором. Требование отдельного одобрения review не вводится для
|
||||||
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
|
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
|
||||||
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
|
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
|
||||||
релиза; их удаление или перенос не входит в обычный процесс.
|
релиза; их удаление или перенос не входит в обычный процесс.
|
||||||
|
|
||||||
`node test-release.cjs` из root проверяет настоящий release-it во временном Git.
|
В root нет собственного test/verify runner и release hooks. Release-it и
|
||||||
Для проверки конфигурации соседа после установки его dependencies:
|
commitlint используются напрямую. Остальные репозитории сохраняют свои gates.
|
||||||
`node test-release.cjs frontend` (аналогично backend, docs, specs).
|
Если в защите веток root ранее были обязательными `verify` и `security`, их
|
||||||
Тест не публикует refs и не использует рабочие базы. Root `npm run verify`
|
нужно убрать из required checks в Gitea; YAML не меняет настройки сервера.
|
||||||
включает tracking и этот тест; frontend/backend/docs сохраняют свои gates,
|
|
||||||
backend дополнительно требует `npm run test:e2e`.
|
|
||||||
|
|
||||||
Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
|
Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
|
||||||
Граница выпуска — принятый коммит, changelog и Git-тег.
|
Граница выпуска — принятый коммит, changelog и Git-тег.
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Requires Compose >= 2.24.4 for !override. Always use a separate project name.
|
||||||
|
name: wealthledger-dev
|
||||||
|
|
||||||
|
x-development-backend: &development-backend
|
||||||
|
image: wealthledger-backend:development
|
||||||
|
pull_policy: build
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
target: development
|
||||||
|
read_only: false
|
||||||
|
environment:
|
||||||
|
NODE_ENV: development
|
||||||
|
volumes: !override
|
||||||
|
- development-data:/data
|
||||||
|
|
||||||
|
services:
|
||||||
|
migration:
|
||||||
|
<<: *development-backend
|
||||||
|
|
||||||
|
backend:
|
||||||
|
<<: *development-backend
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD, node, -e, 'fetch("http://127.0.0.1:3000/api/v1/auth/me").then(async r=>{await r.arrayBuffer();process.exit(r.status===401?0:1)}).catch(()=>process.exit(1))']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
start_period: 20s
|
||||||
|
retries: 6
|
||||||
|
volumes: !override
|
||||||
|
- development-data:/data
|
||||||
|
- ./backend/src:/app/src:ro
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: wealthledger-frontend:development
|
||||||
|
pull_policy: build
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
target: development
|
||||||
|
read_only: false
|
||||||
|
volumes:
|
||||||
|
- ./frontend/src:/app/src:ro
|
||||||
|
- ./frontend/public:/app/public:ro
|
||||||
|
environment:
|
||||||
|
VITE_API_BASE_URL: ''
|
||||||
|
API_PROXY_TARGET: http://backend:3000
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD, node, -e, 'fetch("http://127.0.0.1:8080/").then(async r=>{await r.arrayBuffer();process.exit(r.ok?0:1)}).catch(()=>process.exit(1))']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
start_period: 10s
|
||||||
|
retries: 6
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
development-data:
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: wealthledger
|
||||||
|
|
||||||
|
x-backend: &backend
|
||||||
|
image: ${BACKEND_IMAGE:?Set versioned backend image}
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: '3000'
|
||||||
|
DATABASE_URL: file:/data/wealthledger.db
|
||||||
|
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:?Set browser-visible origin}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:?Set persistent JWT secret}
|
||||||
|
TOKEN_ENCRYPTION_KEY: ${TOKEN_ENCRYPTION_KEY:?Set persistent encryption key}
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: ${DATA_DIRECTORY:?Set persistent absolute host data directory}
|
||||||
|
target: /data
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
read_only: true
|
||||||
|
init: true
|
||||||
|
security_opt: [no-new-privileges:true]
|
||||||
|
cap_drop: [ALL]
|
||||||
|
|
||||||
|
services:
|
||||||
|
migration:
|
||||||
|
<<: *backend
|
||||||
|
command: [node, node_modules/prisma/build/index.js, migrate, deploy]
|
||||||
|
restart: 'no'
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
|
|
||||||
|
backend:
|
||||||
|
<<: *backend
|
||||||
|
scale: 1
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
migration:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: ${FRONTEND_IMAGE:?Set versioned frontend image}
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:${FRONTEND_PORT:-8080}:8080'
|
||||||
|
read_only: true
|
||||||
|
tmpfs: [/tmp]
|
||||||
|
security_opt: [no-new-privileges:true]
|
||||||
|
cap_drop: [ALL]
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
Generated
+14
-14
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "wealth-ledger-workspace",
|
"name": "wealth-ledger-workspace",
|
||||||
"version": "0.1.0",
|
"version": "0.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wealth-ledger-workspace",
|
"name": "wealth-ledger-workspace",
|
||||||
"version": "0.1.0",
|
"version": "0.2.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "21.2.2",
|
"@commitlint/cli": "21.2.2",
|
||||||
"@commitlint/config-conventional": "21.2.2",
|
"@commitlint/config-conventional": "21.2.2",
|
||||||
@@ -1446,9 +1446,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/basic-ftp": {
|
"node_modules/basic-ftp": {
|
||||||
"version": "5.3.1",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-6.2.2.tgz",
|
||||||
"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
|
"integrity": "sha512-LIkJcf/F6bTTfvE3k2YcTxrJsLKKVXiEpHZYkQP6JoqTb7khng7c13ckRTDQbqRh5IkehVH3F00sSlAa2F6DwA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2124,9 +2124,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.1.7",
|
"version": "3.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
|
||||||
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
|
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2355,9 +2355,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "10.7.0",
|
"version": "10.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz",
|
||||||
"integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
|
"integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3439,9 +3439,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.1",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz",
|
||||||
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
|
"integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
+7
-2
@@ -1,12 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "wealth-ledger-workspace",
|
"name": "wealth-ledger-workspace",
|
||||||
"version": "0.1.0",
|
"version": "0.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "24.21.0"
|
"node": "24.21.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"verify": "python3 test-workspace-tracking.py && node test-release.cjs && git diff --check",
|
|
||||||
"release:preview": "release-it --dry-run --ci",
|
"release:preview": "release-it --dry-run --ci",
|
||||||
"release:prepare": "release-it --ci",
|
"release:prepare": "release-it --ci",
|
||||||
"commitlint": "commitlint"
|
"commitlint": "commitlint"
|
||||||
@@ -17,5 +16,11 @@
|
|||||||
"@release-it/conventional-changelog": "12.0.0",
|
"@release-it/conventional-changelog": "12.0.0",
|
||||||
"conventional-changelog-conventionalcommits": "10.4.0",
|
"conventional-changelog-conventionalcommits": "10.4.0",
|
||||||
"release-it": "21.0.2"
|
"release-it": "21.0.2"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"basic-ftp": "6.2.2",
|
||||||
|
"fast-uri": "3.1.8",
|
||||||
|
"ip-address": "10.7.3",
|
||||||
|
"undici": "7.29.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
const assert = require("node:assert/strict");
|
|
||||||
const { execFileSync } = require("node:child_process");
|
|
||||||
const { version } = require("../package.json");
|
|
||||||
const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim();
|
|
||||||
|
|
||||||
assert.equal(git("status", "--porcelain"), "", "Commit all changes before preparing a release");
|
|
||||||
const tags = git("tag", "--merged", "HEAD", "--list", "v[0-9]*", "--sort=-version:refname")
|
|
||||||
.split("\n").filter(tag => /^v\d+\.\d+\.\d+$/.test(tag));
|
|
||||||
assert.equal(version, tags[0]?.slice(1) ?? "0.0.0",
|
|
||||||
"Version is already prepared or release tags are missing. Fetch tags; finish the existing release before preparing another.");
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
// Exercise the actual release command in disposable repositories, never the workspace Git.
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
const { execFileSync, spawnSync } = require('node:child_process');
|
|
||||||
const fs = require('node:fs');
|
|
||||||
const os = require('node:os');
|
|
||||||
const path = require('node:path');
|
|
||||||
const source = path.resolve(process.argv[2] || __dirname);
|
|
||||||
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'wealthledger-release-'));
|
|
||||||
const env = { ...process.env, HUSKY: '0', NO_COLOR: '1' };
|
|
||||||
const git = (...args) => execFileSync('git', args, { cwd, env, encoding: 'utf8' }).trim();
|
|
||||||
const read = file => fs.readFileSync(path.join(cwd, file), 'utf8');
|
|
||||||
const write = (file, text) => fs.writeFileSync(path.join(cwd, file), text);
|
|
||||||
const prepare = (...args) => spawnSync(process.execPath,
|
|
||||||
[path.join(source, 'node_modules/release-it/bin/release-it.js'), '--ci', ...args],
|
|
||||||
{ cwd, env, encoding: 'utf8' });
|
|
||||||
const mustPass = result => assert.equal(result.status, 0, result.stdout + result.stderr);
|
|
||||||
const mustFail = result => assert.notEqual(result.status, 0, result.stdout + result.stderr);
|
|
||||||
const commit = message => git('commit', '--allow-empty', '-qm', message);
|
|
||||||
try {
|
|
||||||
fs.mkdirSync(path.join(cwd, 'scripts'));
|
|
||||||
fs.symlinkSync(path.join(source, 'node_modules'), path.join(cwd, 'node_modules'), 'dir');
|
|
||||||
for (const file of ['.release-it.json', 'scripts/check-release.cjs', 'commitlint.config.cjs']) {
|
|
||||||
fs.copyFileSync(path.join(source, file), path.join(cwd, file));
|
|
||||||
}
|
|
||||||
write('.gitignore', 'node_modules/\nremote.git/\n');
|
|
||||||
write('CHANGELOG.md', '# Changelog\n');
|
|
||||||
write('package.json', JSON.stringify({ name: 'release-fixture', private: true, version: '0.0.0' }));
|
|
||||||
write('package-lock.json', JSON.stringify({ name: 'release-fixture', version: '0.0.0', lockfileVersion: 3, packages: { '': { name: 'release-fixture', version: '0.0.0' } } }));
|
|
||||||
git('init', '-q', '-b', 'main');
|
|
||||||
git('config', 'user.name', 'Release test');
|
|
||||||
git('config', 'user.email', 'release@example.invalid');
|
|
||||||
git('config', 'core.hooksPath', '/dev/null');
|
|
||||||
git('init', '--bare', '-q', 'remote.git');
|
|
||||||
git('remote', 'add', 'origin', path.join(cwd, 'remote.git'));
|
|
||||||
git('add', '.');
|
|
||||||
commit('chore: initialize fixture');
|
|
||||||
mustFail(prepare('0.1.0')); // main cannot prepare releases.
|
|
||||||
git('switch', '-qc', 'release/test');
|
|
||||||
write('untracked.txt', 'must block\n');
|
|
||||||
mustFail(prepare('0.1.0'));
|
|
||||||
fs.unlinkSync(path.join(cwd, 'untracked.txt'));
|
|
||||||
const before = ['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read);
|
|
||||||
const head = git('rev-parse', 'HEAD');
|
|
||||||
mustPass(prepare('0.1.0', '--dry-run'));
|
|
||||||
assert.deepEqual(['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read), before);
|
|
||||||
assert.equal(git('rev-parse', 'HEAD'), head);
|
|
||||||
mustPass(prepare('0.1.0'));
|
|
||||||
assert.equal(git('tag', '--list'), '', 'Preparation must not create tags');
|
|
||||||
assert.equal(git('log', '-1', '--format=%s'), 'chore(release): 0.1.0');
|
|
||||||
mustFail(prepare()); // Prepared version cannot be bumped again before tagging.
|
|
||||||
git('tag', '-a', 'v0.1.0', '-m', 'Release 0.1.0');
|
|
||||||
mustFail(prepare()); // No new commits.
|
|
||||||
for (const [message, expected] of [
|
|
||||||
['fix: correct calculation', '0.1.1'],
|
|
||||||
['feat: add export', '0.2.0'],
|
|
||||||
['docs: explain export', '0.2.1'],
|
|
||||||
['spec: define export', '0.2.2'],
|
|
||||||
['chore(deps): update dependencies', '0.2.3'],
|
|
||||||
['fix!: remove legacy contract', '1.0.0'],
|
|
||||||
['feat: change export\n\nBREAKING CHANGE: replace export format', '2.0.0'],
|
|
||||||
]) {
|
|
||||||
commit(message);
|
|
||||||
mustPass(prepare());
|
|
||||||
const pkg = JSON.parse(read('package.json'));
|
|
||||||
const lock = JSON.parse(read('package-lock.json'));
|
|
||||||
assert.equal(pkg.version, expected);
|
|
||||||
assert.equal(lock.version, expected);
|
|
||||||
assert.equal(lock.packages[''].version, expected);
|
|
||||||
assert.ok(read('CHANGELOG.md').includes(expected));
|
|
||||||
assert.equal(git('status', '--porcelain'), '');
|
|
||||||
git('tag', '-a', `v${expected}`, '-m', `Release ${expected}`);
|
|
||||||
}
|
|
||||||
assert.ok(read('CHANGELOG.md').includes('/commit/'), read('CHANGELOG.md'));
|
|
||||||
assert.ok(read('CHANGELOG.md').includes('/compare/v0.1.0...v0.1.1'));
|
|
||||||
assert.ok(!read('CHANGELOG.md').includes('chore(release)'));
|
|
||||||
assert.equal(read('CHANGELOG.md').split('correct calculation').length, 2, 'Prior changes must not be repeated');
|
|
||||||
assert.equal(spawnSync('git', ['--git-dir=remote.git', 'show-ref'], { cwd }).status, 1, 'Preparation must not push refs');
|
|
||||||
const lint = message => spawnSync(process.execPath,
|
|
||||||
[path.join(source, 'node_modules/@commitlint/cli/cli.js'), '--strict'],
|
|
||||||
{ cwd, env, input: message, encoding: 'utf8' });
|
|
||||||
mustPass(lint('spec: define contract'));
|
|
||||||
mustFail(lint('unstructured message'));
|
|
||||||
console.log(`PASS: release preparation, guards, version rules, lockfile and changelog (${source})`);
|
|
||||||
} finally {
|
|
||||||
fs.rmSync(cwd, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
"""Check root tracking on fictional data; never stage the real workspace."""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
|
|
||||||
def check(ignore_rules):
|
|
||||||
with tempfile.TemporaryDirectory(prefix="wealthledger-tracking-") as directory:
|
|
||||||
root = Path(directory)
|
|
||||||
|
|
||||||
def git(*args):
|
|
||||||
return subprocess.check_output(
|
|
||||||
["git", "-C", str(root), *args], text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
git("init", "--quiet")
|
|
||||||
allowed = {
|
|
||||||
'.gitea/workflows/commits.yml',
|
|
||||||
'.gitea/workflows/security.yml',
|
|
||||||
'.gitea/workflows/verify.yml',
|
|
||||||
'.gitignore',
|
|
||||||
'.nvmrc',
|
|
||||||
'.release-it.json',
|
|
||||||
'AGENTS.md',
|
|
||||||
'CHANGELOG.md',
|
|
||||||
'README.md',
|
|
||||||
'commitlint.config.cjs',
|
|
||||||
'package-lock.json',
|
|
||||||
'package.json',
|
|
||||||
'renovate.json',
|
|
||||||
'scripts/check-release.cjs',
|
|
||||||
'test-release.cjs',
|
|
||||||
'test-workspace-tracking.py',
|
|
||||||
}
|
|
||||||
for name in allowed:
|
|
||||||
(root / name).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
(root / name).write_text("fictional fixture\n")
|
|
||||||
(root / ".gitignore").write_text(ignore_rules)
|
|
||||||
for name in ("frontend", "backend", "docs", "specs"):
|
|
||||||
nested = root / name
|
|
||||||
nested.mkdir()
|
|
||||||
subprocess.run(["git", "init", "--quiet", str(nested)], check=True)
|
|
||||||
for name in (
|
|
||||||
"frontend/source.ts", "backend/prisma/dev.db", "docs/README.md",
|
|
||||||
"specs/README.md", "statement-imports/report.csv", "private.db",
|
|
||||||
"private.db-wal", ".env", ".env.example", "audit.pdf",
|
|
||||||
"design/preview.html", "todo/notes.md", "references/source.md",
|
|
||||||
".agents/local.md", "unknown-directory/data.txt", "unknown-file.txt",
|
|
||||||
"scripts/private.txt", ".gitea/private.txt", ".gitea/workflows/private.yml",
|
|
||||||
"node_modules/private.txt",
|
|
||||||
):
|
|
||||||
target = root / name
|
|
||||||
target.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
target.write_text("fictional private data\n")
|
|
||||||
before = {p.relative_to(root): p.read_bytes() for p in root.rglob("*")
|
|
||||||
if p.is_file() and ".git" not in p.relative_to(root).parts[:1]}
|
|
||||||
git("add", ".")
|
|
||||||
assert set(git("ls-files").splitlines()) == allowed
|
|
||||||
assert all((root / p).read_bytes() == content for p, content in before.items())
|
|
||||||
assert all((root / name / ".git").is_dir()
|
|
||||||
for name in ("frontend", "backend", "docs", "specs"))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
check(Path(__file__).with_name(".gitignore").read_text())
|
|
||||||
print("PASS: only root allowlist staged; fictional data and nested Git preserved")
|
|
||||||
Reference in New Issue
Block a user