261 lines
28 KiB
Markdown
261 lines
28 KiB
Markdown
# Перенос сервисов АРМ КА в контур банка: гайд для разработчика
|
||
|
||
Разработка ведётся **снаружи** (workspace `awp-ca`, Gitea проекта). В контур банка (GitLab платформы РАИСА и его CI/CD;
|
||
Gitea в контуре нет)
|
||
уезжают только три сервисных репозитория — `arm-core`, `tabular-parser`, `scan-gateway` — **снимками дерева, без
|
||
истории**. Репозитории `docs` и `fixtures` в контур не переносятся: решение владельца 06.10.2026. Этот документ — что
|
||
именно уезжает, чего в платформе нет, как подготовить снимок скриптом и как его положить в контур.
|
||
|
||
Соглашения платформы, от которых всё отталкивается, — `context/03-architecture.md`, разделы «Соглашения шаблона команды»
|
||
и «Что ожидаем от платформы». Проверки качества — `dev/checks.md`.
|
||
|
||
## 1. Что переносится и что нет
|
||
|
||
| Что | В контур | Почему |
|
||
|---|---|---|
|
||
| `api/` (код, тесты, `contracts/`, `rules/`, `alembic/`, `scripts/`), `web/` (arm-core), `README.md`, `Makefile`, `setup/`, `raisa_config.yml`, `api/.env.example`, `api/.env.test` | да | это и есть сервис по шаблону платформы |
|
||
| `openspec/specs/`, `openspec/changes/archive/` (и `web/openspec/…` в arm-core) | да | спецификация системы и журнал решений (`design.md`); без них код в контуре нечем объяснять |
|
||
| `.claude/`, `.codegraph/`, `CLAUDE.md`, `openspec/config.yaml` — на любой глубине, в т.ч. `web/` arm-core | **нет** | инструкции и контекст AI-агента (в `config.yaml` — блоки `context`/`rules` с путями `../docs`, `../fixtures`), инструментарий внешней разработки; `openspec` CLI в контуре нет, `specs/` и `design.md` читаются без него |
|
||
| `docs/superpowers/` (планы, ledger) — на любой глубине | **нет** | рабочие артефакты процесса, содержат локальные пути разработчика |
|
||
| `Dockerfile`, `.dockerignore` | **нет** | образ для локального `make check-full`; в контуре образ собирает платформа своим CI (шаблон держит `Dockerfile` в `.gitignore`) |
|
||
| История коммитов | **нет** | снимок одним коммитом; в истории снаружи — трейлеры `Co-Authored-By: Claude …`, `Claude-Session`, ссылки на внешний Gitea |
|
||
| Репозиторий `docs` | **нет** | контекст, модель, контракты, экспорт ТЗ — остаются снаружи; копия контракта лежит в `api/contracts/` каждого сервиса |
|
||
| Репозиторий `fixtures` | **нет** | реальная отчётность для регрессии парсера; тесты с маркером `fixtures` в контуре пропускаются |
|
||
| `reference-old-arm` | **нет** | старый репозиторий банка, читался только для справки |
|
||
|
||
Снимок получает файл `SNAPSHOT.md` с хешем коммита-источника и датой: по нему потом сверяется, что уже перенесено.
|
||
|
||
## 2. Чего в платформе нет и что есть
|
||
|
||
**Нет в контуре (всё это скрипт вырезает или заменяет):**
|
||
|
||
- `uv` — `make venv` снаружи ставит окружение через `uv venv --python 3.11` и `uv pip`. В снимке цель `venv` переписана
|
||
на `python3.11 -m venv` + `pip` с `setup/pip.conf` (Nexus), как делает `setup/setup.sh` шаблона.
|
||
- Docker и `docker compose` — локальная инфраструктура (`docs/dev/docker-compose.yml`) живёт в `docs`, которого в контуре
|
||
нет. Цели `make up`, `test-integration`, `check-full` в контуре не запускаются; `docker-build` удалена из `Makefile`.
|
||
- `docs` и `fixtures` рядом с сервисом — тесты идентичности контракта и тесты на реальных данных пропускаются
|
||
(раздел 5), сверка ORM с DBML (`make dbml-check` в arm-core) не выполняется.
|
||
- `tea`, `codegraph`, `openspec` (CLI), хуки `.claude/hooks` — инструментарий процесса; его конфигурация
|
||
(`.claude/`, `.codegraph/`, `CLAUDE.md`, `openspec/config.yaml`) вырезается на любой глубине — фронт в `arm-core/web/`
|
||
синхронизируется из arm-web вместе со своими копиями этих файлов, удалять их вручную не нужно.
|
||
- Внешний Gitea `git.aepif.ru` — ссылки вида `https://git.aepif.ru/RSHB/arm-core/pulls/19` в архивах change'ей
|
||
скрипт превращает в `RSHB/arm-core#19` (номера относятся к внешнему Gitea, в контуре их нет).
|
||
|
||
**Есть только в контуре (снаружи заменено заглушками, в снимке остаётся как есть):**
|
||
|
||
- Nexus `nexus.dev.raisa.go.rshbank.ru` (`setup/pip.conf`, `setup/.npmrc`) и пакеты `raisa_fastapi_protected_api`
|
||
(`api/requirements.txt` arm-core), `raisa-fastapi-protected-web`, `raisa-ui` (`web/package.json`, `optionalDependencies`).
|
||
Снаружи `make venv` их пропускает (`grep -v '^raisa_'`), импорт `raisa_fastapi_protected_api` условный — только при
|
||
`DEBUG=false` (в контуре временно `DEBUG=true`, см. 6.3). В контуре всё ставится штатно.
|
||
- Запуск платформой: `api/run_server.py`, `SERVER__HOST`/`SERVER__PORT`, ресурсы `raisa_config.yml`, секреты и настройки `OPENBAO__*`
|
||
из OpenBao (раздел 6), Python 3.11 в базовом образе.
|
||
- PostgreSQL 16 (схема `dkb`), NATS без JetStream (nkeys/JWT), S3-совместимое хранилище, вход временно через dev-заглушку, позже JWT РАИСА (раздел 6.3).
|
||
|
||
## 3. Подготовка снимка: `scripts/prepare-contour-snapshot.sh`
|
||
|
||
Запуск из workspace, по одному репозиторию:
|
||
|
||
```bash
|
||
docs/scripts/prepare-contour-snapshot.sh arm-core /tmp/contour
|
||
docs/scripts/prepare-contour-snapshot.sh tabular-parser /tmp/contour
|
||
docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour
|
||
# третий аргумент — ref; по умолчанию origin/main (снимок всегда с сервера, не из рабочего дерева)
|
||
```
|
||
|
||
Что делает скрипт (репозиторий-источник не меняется):
|
||
|
||
0. Только arm-core: проверяет, не отстаёт ли встроенный фронт `web/` от `RSHB/arm-web@test` (`sync-web.sh --check`,
|
||
раздел 3.1). Если отстаёт — спрашивает «перенести как есть? [y/N]»: `y` — снимок делается, отставание записывается в
|
||
`SNAPSHOT.md`; `n` (или пустой ответ) — запускается синхронизация (worktree, issue, PR в arm-core) и перенос
|
||
останавливается с кодом 3 до merge PR и закрытия issue («закрывай»), затем снимок повторяется. Без терминала вопрос не
|
||
задаётся: нужен флаг `--web-as-is` первым аргументом, иначе код 3. Если `web/` правили напрямую в arm-core мимо arm-web
|
||
(точки синхронизации нет) — снимок не делается, разбирать вручную. Если `arm-web` рядом не найден — предупреждение,
|
||
снимок делается.
|
||
1. `git archive <ref>` → `/tmp/contour/<repo>/` — дерево коммита без `.git` и без игнорируемых файлов.
|
||
2. Удаляет `.claude/`, `.codegraph/`, `CLAUDE.md`, `openspec/config.yaml`, `docs/superpowers/` на любой глубине (в т.ч.
|
||
`web/` arm-core; опустевший `docs/` тоже), `Dockerfile` и `.dockerignore` — в корне.
|
||
3. Правит `Makefile`: цель `venv` → `python3.11 -m venv` + `pip install -r requirements-dev.txt` с `setup/pip.conf`
|
||
(переменная `PYTHON`, по умолчанию `python3.11`); удаляет цель `docker-build` и её упоминания в `check-full`, `.PHONY`,
|
||
`help`.
|
||
4. Заменяет ссылки на внешний Gitea на `RSHB/<repo>#<n>`.
|
||
5. Пишет `SNAPSHOT.md` (источник, хеш, дата, что вырезано).
|
||
6. Проверяет: нет `.claude`/`.codegraph`/`CLAUDE.md`/`openspec/config.yaml`/планов ни на какой глубине, нет `Dockerfile`, нет локальных путей (`/Users/…`, `/home/…`), нет `uv` и
|
||
`git.aepif.ru`, на месте платформенные файлы (`raisa_config.yml`, `setup/`, `run_server.py`, `requirements.txt`,
|
||
`.env.test`, `contracts/`). Любая проблема — код возврата 1 и список.
|
||
|
||
Тест скрипта — `make test-snapshot` (синтетический репозиторий с артефактами агента в корне и в `web/`); проверка
|
||
отставания `web/` и остановка переноса — в `make test-sync-web`.
|
||
|
||
### 3.1. Синхронизация `arm-core/web` с `arm-web`: `scripts/sync-web.sh`
|
||
|
||
Фронт ведётся в двух местах: отдельный репозиторий `RSHB/arm-web` (ветка `test`, прототип и демо-стенд) и встроенный
|
||
`arm-core/web`, из которого собирается контур. Правила: `arm-core/web` напрямую не редактируется — все правки фронта идут в
|
||
arm-web и переносятся скриптом; состояние синхронизации не хранится — точкой считается коммит arm-web, у которого дерево
|
||
`src` совпадает с `web/src` в arm-core main (сравнение по хэшу дерева git).
|
||
|
||
```bash
|
||
make -C docs sync-web SYNC_ARGS=--check # 0 — синхронно; 10 — arm-core/web отстаёт (список коммитов); 20 — расхождение
|
||
make -C docs sync-web SYNC_ARGS=--dry-run # worktree .wt/web-sync-<sha7>/arm-core с перенесённым набором, без issue/PR
|
||
make -C docs sync-web # полный цикл: worktree, issue и PR в RSHB/arm-core, проверки, коммит, push
|
||
```
|
||
|
||
Полный цикл делает то же, что делалось вручную в arm-core #94 и #100: worktree по абсолютному пути на ветке
|
||
`feat/<issue>-web-sync-<sha7>` с `.bounded`; перенос набора синхронизации — всё дерево arm-web, кроме служебного
|
||
(`CLAUDE.md`, `.claude/`, `openspec/`, `docs/`, `scripts/`, `Makefile`, `ARCHITECTURE.md`, `.gitignore`, `package-lock.json`;
|
||
`README.md` входит в набор — решение владельца 10.10.2026); контроль `diff -rq` с деревом arm-web; проверки `vitest`,
|
||
`tsc --noEmit`, `biome check src` (предупреждений не больше, чем на main), `rsbuild build`; коммит и PR с диапазоном
|
||
коммитов arm-web и рулингами R-1/R-2. Любая красная проверка останавливает скрипт до коммита, worktree остаётся для разбора.
|
||
|
||
`node_modules` для проверок берётся из `WEB_NODE_MODULES` (по умолчанию `.wt/web-proto/arm-web/node_modules` — стенд демо),
|
||
иначе `npm install` в worktree. Скрипт ничего не мержит и не удаляет: ревью перенесённого кода против бэкенда, merge и
|
||
чеклист «закрывай» — за исполнителем. Код 20 (расхождение) означает прямые правки `web/` в arm-core: их нужно перенести в
|
||
arm-web или откатить, иначе автоперенос их затёр бы.
|
||
|
||
Проверено 06.10.2026: снимки arm-core@1d7f2c8, tabular-parser@3b32462, scan-gateway@dffc18a проходят `make check` без
|
||
`docs` и `fixtures` (1084 / 1087 / 726 passed, пропуски только по причинам из раздела 5).
|
||
|
||
## 4. Как положить снимок в контур
|
||
|
||
Репозитории контура — проекты GitLab, по одному на сервис, имена как снаружи. Первый перенос и каждый следующий делаются
|
||
одинаково (вместо PR в GitLab — merge request, в остальном git тот же):
|
||
|
||
```bash
|
||
git clone <url контура>/arm-core contour-arm-core
|
||
cd contour-arm-core && git switch -c sync/arm-core-1d7f2c8 # ветка по правилам контура
|
||
rsync -a --delete --exclude .git /tmp/contour/arm-core/ ./ # дерево = снимок, удалённые снаружи файлы исчезают
|
||
git status --short # прочитать diff: он должен совпадать с изменениями снаружи
|
||
git add -A
|
||
git commit -m "sync: arm-core@1d7f2c8 (2026-10-06)" # хеш и дата — из SNAPSHOT.md
|
||
git push -u origin sync/arm-core-1d7f2c8 # дальше merge request и мерж по регламенту GitLab контура
|
||
```
|
||
|
||
`--delete` обязателен: иначе файлы, удалённые или переименованные снаружи, останутся в контуре. Если в контуре делали
|
||
локальные правки (раздел 7), сначала вернуть их наружу, иначе `rsync --delete` их перезапишет.
|
||
|
||
Порядок сервисов при одной синхронизации: `arm-core` → `tabular-parser` → `scan-gateway` (ядро — потребитель контракта,
|
||
его копия схемы должна быть не старше копий сервисов). Если снаружи поднималась версия контракта (`api/contracts/*.json`,
|
||
`CONTRACT_VERSION` в `api/src/contract/common.py`), переносить все три сервиса одним заходом.
|
||
|
||
## 5. Проверка в контуре перед merge request
|
||
|
||
```bash
|
||
make venv # python3.11 -m venv + pip через Nexus (вместо setup/setup.sh; тот ставит только прод-зависимости)
|
||
make check # ruff, mypy, pytest без интеграционных тестов
|
||
```
|
||
|
||
Ожидаемые пропуски (`pytest -rs` покажет причины), это **не** ошибки:
|
||
|
||
| Сервис | Пропускается | Причина |
|
||
|---|---|---|
|
||
| все | тест идентичности `api/contracts/` с `docs/contracts/` | `docs` нет; идентичность проверена снаружи перед снимком |
|
||
| все | тесты с маркером `fixtures` (golden-снимки, property-тесты на реальных ОСВ/формах, покрытие словарей) | `fixtures` нет; регрессия на реальных данных — снаружи |
|
||
| arm-core | сверка ORM с DBML в тестах | DBML в `docs` |
|
||
| scan-gateway | тесты с реальными ответами ИС Сканирование (`fixtures/ocr`) | то же |
|
||
|
||
Не запускать в контуре: `make up`, `make test-integration`, `make check-full`, `make dbml-check`, `make e2e` — им нужны
|
||
compose из `docs` либо Docker. Интеграционные тесты против PostgreSQL/NATS/MinIO прогоняются снаружи перед каждым снимком;
|
||
пайплайну платформы отдаётся `make check`.
|
||
|
||
Фронт arm-core: `make web-check` (`npm install` через `.npmrc` Nexus, biome, tsc, `npm run build`). В контуре
|
||
`raisa-fastapi-protected-web` и `raisa-ui` ставятся штатно, снаружи они в `optionalDependencies` и пропускаются.
|
||
`web/package-lock.json` снаружи не коммитится (OQ-63) — в контуре lock-файл появится при первом `npm install`, решение о его
|
||
коммите за контуром.
|
||
|
||
## 6. Что нужно от платформы для запуска
|
||
|
||
### 6.1. Как задаются настройки: только через OpenBao
|
||
|
||
Платформа РАИСА передаёт в под **только секреты OpenBao**; обычные переменные окружения (`DEBUG`, `NATS_ENABLED`, ...)
|
||
задать нельзя (ответ разработчика переноса, 07.10.2026). Поэтому любая настройка верхнего уровня `Settings` задаётся
|
||
как **`OPENBAO__APP__<ИМЯ>`** (arm-core PR #49, issue #44; arm-scan-gateway PR #12, issue #11; arm-tabular-parser PR #23,
|
||
issue #22; файл `api/src/core/settings_sources.py` одинаков в трёх сервисах). Правила:
|
||
|
||
- приоритет источников: обычная переменная окружения → `OPENBAO__APP__*` → `api/.env` → значение по умолчанию;
|
||
- `OPENBAO__APP__*` читается только из окружения процесса, в `.env` не действует;
|
||
- имя должно существовать в `Settings`; неизвестное имя — ошибка при старте (опечатка не остаётся незамеченной);
|
||
- вложенные настройки — через `__`: `OPENBAO__APP__SERVER__PORT`;
|
||
- `OPENBAO__APP__OPENBAO__...` запрещено: секреты задаются только как `OPENBAO__<ГРУППА>__<ИМЯ>`.
|
||
|
||
Имена и смысл настроек — `api/.env.example` каждого сервиса. Секреты (общие для всех сервисов: `OPENBAO__MINIO__*`,
|
||
`OPENBAO__NATS__*`) задаются как раньше.
|
||
|
||
### 6.2. Значения контура по сервисам
|
||
|
||
| Сервис | Секреты `OPENBAO__<ГРУППА>__*` | Настройки `OPENBAO__APP__*` |
|
||
|---|---|---|
|
||
| arm-core | `OPENBAO__DATABASE__DATABASE_URL` (asyncpg, схема `dkb`), `OPENBAO__MINIO__{MINIO_URL, MINIO_ADMIN_LOGIN, MINIO_ADMIN_PASSWORD, MINIO_BUCKET}` (`MINIO_SECURE=true` по умолчанию), `OPENBAO__NATS__{NATS_URL, NATS_SEED, NATS_JWT, NATS_TABULAR_REQUEST_SUBJECT, NATS_IMAGE_REQUEST_SUBJECT, NATS_RESULT_SUBJECT}` | `NATS_ENABLED=true` (по умолчанию `false`: без него NATS не подключается и обмен с сервисами распознавания не работает), `DEBUG=true`, `AUTH_DEV_ISSUER=true`, `AUTH_DEV_SECRET=<случайная строка от 32 байт>` (поле верхнего уровня `Settings`, поэтому тоже через `OPENBAO__APP__`), `FORWARDED_ALLOW_IPS=<адреса прокси платформы>` (иначе IP в аудите — адрес прокси); `CORS_ORIGINS` при `DEBUG=true` не нужен. `SERVER__HOST` не задавать (платформа задаёт сама). **Временно, на период тестов:** `CONSOLIDATION_PERIOD_FROM_VERSION=true` — ОСВ, загруженная в консолидацию, берёт её период (arm-core#103, OQ-161; `qa-guide.md` §7.4); после тестов убрать |
|
||
| scan-gateway | `OPENBAO__MINIO__*`, `OPENBAO__NATS__*` (включая три subject'а), `OPENBAO__SMB__{SMB_HOST, SMB_PATH, SMB_LOGIN, SMB_PASSWORD}` | `SCAN_HOTFOLDER_MODE=smb` (`NATS_ENABLED` по умолчанию `true`, задавать не нужно). `SCAN_HOTFOLDER_PATH` в контуре не нужен (только режим `local`; режим `local` и эмулятор `make emulator` — для разработки) |
|
||
| tabular-parser | `OPENBAO__MINIO__*`, `OPENBAO__NATS__*` (включая три subject'а) | сверх секретов ничего (`NATS_ENABLED` по умолчанию `true`); при необходимости `PARSE_TIMEOUT_SECONDS` (и `PARSE_MAX_BYTES`) |
|
||
|
||
Для тестового контура — свой набор JWT, seed и трёх subject'ов (`NATS_TABULAR_REQUEST_SUBJECT` по умолчанию
|
||
`files.tabular_uploaded`, `NATS_IMAGE_REQUEST_SUBJECT` — `files.img_uploaded`, `NATS_RESULT_SUBJECT` —
|
||
`files.tabular_parsed`): одинаковый у всех трёх сервисов; JWT должен давать publish/subscribe на эти subject'ы.
|
||
Разъехавшиеся настройки — тишина на шине (dlq по NO_ACK); сверяйте `/healthcheck.subjects`.
|
||
|
||
В отличие от arm-core, в `scan-gateway` и `tabular-parser` `NATS_ENABLED=true` по умолчанию, а `DEBUG` влияет только на
|
||
уровень логов; платформенной аутентификации у них нет (внутренние сервисы без HTTP-входа для пользователей).
|
||
|
||
Зависимости под контур: `PyJWT[crypto]==2.8.0` и `cryptography==43.0.3` (arm-core PR #50, issue #43; в том же PR
|
||
исправлена проверка JWKS на PyJWT 2.8.0).
|
||
|
||
Миграции и seed при деплое: из `api/` — `alembic upgrade head`, затем `python -m scripts.seed` (справочники: план счетов,
|
||
правила элиминации, роли и права; `--dev` только для стенда разработки). Те же команды — цели `make migrate` и `make seed`
|
||
(`README.md` arm-core, «Быстрый старт», шаг 4 «Миграции и справочники»).
|
||
|
||
Subjects NATS, схема ключей S3 и ресурсы — `context/03-architecture.md`.
|
||
|
||
### 6.3. Аутентификация в контуре (временно)
|
||
|
||
Контур временно **без платформенной аутентификации**: приложение доступно любому, кто его открыл. Вход идёт через
|
||
dev-заглушку: `POST /api/v1/dev/token {email}` выдаёт JWT, на фронте её использует форма DevLogin. Что для этого включено:
|
||
|
||
- `OPENBAO__APP__DEBUG=true` дополнительно включает CORS `*` и открытые `/docs`, `/redoc`, `/openapi.json`;
|
||
middleware `raisa_fastapi_protected_api` не импортируется (в arm-core он и так не подключён);
|
||
- `OPENBAO__APP__AUTH_DEV_ISSUER=true` подключает роутер dev-токенов (нужны оба флага: `DEBUG` и `AUTH_DEV_ISSUER`);
|
||
токены подписываются `AUTH_DEV_SECRET`.
|
||
|
||
Последствия, которые нужно понимать:
|
||
|
||
- пользователи с ролями должны быть в `app_user` **до входа** (seed); войти может любой, кто знает email пользователя,
|
||
то есть защита контура держится на сетевой изоляции, а не на аутентификации;
|
||
- `AUTH_DEV_SECRET` — секрет: случайная строка от 32 байт, хранится в OpenBao, в код и в `.env.example` не попадает.
|
||
|
||
Переход на SSO РАИСА (отключить `DEBUG`/`AUTH_DEV_ISSUER`, задать `AUTH_JWKS_URL` или `AUTH_PUBLIC_KEY` + `AUTH_ISSUER`,
|
||
`AUTH_AUDIENCE`, `AUTH_EMAIL_CLAIM`) — после ответа на OQ-54 (`context/07-open-questions.md`) и задачи arm-core#43.
|
||
|
||
## 7. Правки внутри контура и обратный поток
|
||
|
||
Правило: источник истины — репозитории снаружи. Любая правка в контуре (чаще всего под пайплайн платформы: пути, переменные,
|
||
`raisa_config.yml`) возвращается наружу до следующей синхронизации:
|
||
|
||
```bash
|
||
cd contour-arm-core
|
||
git diff <коммит sync> HEAD -- . ':!SNAPSHOT.md' > /tmp/contour-arm-core.patch # база — коммит последнего sync
|
||
```
|
||
|
||
Патч применяется снаружи в ветку и проходит обычный PR; после его мержа следующий снимок уже содержит правку, и
|
||
`rsync --delete` ничего не потеряет. Если правка касается только контура (например, секретов платформы), её место — вне
|
||
репозитория: значения в OpenBao (`OPENBAO__*`), а не код.
|
||
|
||
## 8. Чек-лист одной синхронизации
|
||
|
||
1. Снаружи: нужные PR слиты в `main` трёх сервисов; `make check-full` зелёный в каждом; контракт в `docs/contracts` и копии
|
||
`api/contracts` идентичны.
|
||
2. Правки из контура (раздел 7) возвращены наружу и слиты.
|
||
3. `prepare-contour-snapshot.sh` по каждому сервису; скрипт завершился словами «снимок готов». Для arm-core скрипт сам
|
||
проверяет отставание `web/` от arm-web (раздел 3.1): при остановке с кодом 3 — дождаться merge PR синхронизации и
|
||
«закрывай», затем повторить.
|
||
4. В контуре: клон, ветка, `rsync --delete`, просмотр `git status`, коммит `sync: <repo>@<hash> (<дата>)`, `make venv`,
|
||
`make check` (пропуски — только из раздела 5), для arm-core `make web-check`, merge request по регламенту GitLab контура.
|
||
5. Порядок мержа при смене версии контракта: arm-core → tabular-parser → scan-gateway.
|
||
6. Снаружи в `context/09-delivery-plan.md` или в issue синхронизации отметить, какие хеши перенесены.
|
||
|
||
## 9. Известные неточности снимка
|
||
|
||
- `README.md` сервисов описывают `make venv` через `uv` и ссылаются на `../docs`, `../fixtures`, `docs/dev/qa-guide.md`;
|
||
скрипт их не переписывает. Ориентир для контура — этот документ и `SNAPSHOT.md`.
|
||
- В `openspec/changes/archive/*/` остаются относительные пути вида `../docs/openspec/...` и `../fixtures/...`: это
|
||
ссылки на внешний workspace, в контуре они никуда не ведут.
|
||
- Тесты, которые снаружи пропускаются без `fixtures`, в контуре не выполняются никогда. Поэтому перед каждым снимком снаружи
|
||
обязателен `make check-full` с доступными `docs` и `fixtures` (`dev/checks.md`, раздел «Порядок перед PR»).
|