docs: фронт в arm-core/web — убрать sync-web, шаг 0 снимка, arm-web из гайдов; demo-prepare.sh в docs/scripts (#143)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-21 09:53:01 +04:00
parent bc51c03a66
commit 273e3053b8
15 changed files with 129 additions and 518 deletions

View File

@ -8,7 +8,7 @@ CHANGE ?=
# Текущая версия модели данных — по ней рисуются диаграммы (make diagrams)
DBML ?= model/data-model-v5.19.dbml
.PHONY: venv validate-contracts validate-openspec test-snapshot test-sync-web test-sync-statements sync-web sync-statements statements-check check diagrams
.PHONY: venv validate-contracts validate-openspec test-snapshot test-sync-statements sync-statements statements-check check diagrams
# Зависимости скриптов проверки контракта — jsonschema и PyYAML (перечни кодов форм contracts/statement-code-orders.yaml)
venv:
@ -27,18 +27,10 @@ validate-openspec:
test-snapshot:
STATEMENTS_PY=$(PY) scripts/test_prepare_contour_snapshot.sh
# sync-web.sh на синтетических репозиториях: точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса
test-sync-web:
scripts/test_sync_web.sh
# sync-statements.sh на синтетических репозиториях: помощник проверок, копирование пакета, отказы
test-sync-statements:
STATEMENTS_PY=$(PY) scripts/test_sync_statements.sh
# Синхронизация arm-core/web с arm-web@test одной командой (worktree, issue, PR); SYNC_ARGS="--check" или "--dry-run"
sync-web:
scripts/sync-web.sh $(SYNC_ARGS)
# Копия пакета arm_statements в хост одной командой (worktree, issue, PR); SYNC_ARGS="--host arm-core [--check|--dry-run]"
sync-statements:
STATEMENTS_PY=$(PY) scripts/sync-statements.sh $(SYNC_ARGS)
@ -50,7 +42,7 @@ statements-check:
STATEMENTS_PY=$(PY) scripts/sync-statements.sh --check --host $$h || { c=$$?; [ $$c -eq 10 ] || rc=1; }; \
done; exit $$rc
check: validate-contracts validate-openspec test-snapshot test-sync-web test-sync-statements
check: validate-contracts validate-openspec test-snapshot test-sync-statements
# Диаграммы model/diagrams/part01…09.{svg,png} из $(DBML); нужен npx (Node.js), зависимостей Python нет
diagrams:

View File

@ -26,10 +26,9 @@
**Фронт ведётся в `arm-core/web`** (решение владельца 21.10.2026): фронтовые задачи — worktree arm-core и PR в
`RSHB/arm-core` → `main`, туда же идут PR фронта от других исполнителей. Репозиторий `RSHB/arm-web` (ветка `test`)
заморожен на `de4e477` — последней синхронизации (arm-core #147, `3157a15`); новых правок туда не вносим. Скрипт
`make sync-web` (`scripts/sync-web.sh`, `dev/contour-transfer-guide.md` раздел 3.1) больше не используется: его
`--check` теперь показывает расхождение (код 20), поэтому `scripts/prepare-contour-snapshot.sh` для arm-core
запускается с `--web-as-is`. Спеки экранов (`*-ui`) пока лежат в `arm-web/openspec/specs/`; новые требования к экранам —
в `arm-core/openspec/`.
синхронизации `sync-web.sh` удалён, `scripts/prepare-contour-snapshot.sh` берёт `web/` из arm-core как есть
(`dev/contour-transfer-guide.md` раздел 3.1). Спеки экранов (`*-ui`) пока лежат в `arm-web/openspec/specs/`; новые
требования к экранам — в `arm-core/openspec/`.
## Как агент работает с несколькими репозиториями

View File

@ -74,11 +74,11 @@ docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka run --rm
- **Маркер версии:** `api/arm_statements/VERSION` — полный хэш коммита `arm-statements`, из которого разложена копия;
пишет только скрипт. Сервис при старте читает его и пишет в результат `service.statements_version`; ядро сравнивает его
с `statements_version` задания при пересчёте после правки (`STATEMENTS_VERSION_MISMATCH`, `info`).
- **Скрипт:** `docs/scripts/sync-statements.sh` (по образцу `sync-web.sh`), запуск из workspace или `make -C docs
- **Скрипт:** `docs/scripts/sync-statements.sh`, запуск из workspace или `make -C docs
sync-statements SYNC_ARGS=…`. Источник — локальный checkout `arm-statements` рядом (`../arm-statements`) или
`--source <dir>`, ссылка — `origin/main` или `--ref <ref>`; хост — `--host arm-core|tabular-parser|scan-gateway` или
`--host-dir <dir>`. Без `--check` — раскладывает копию нужного коммита в новый worktree хоста на ветке фичи и пишет
`VERSION`; issue и PR — как у `sync-web.sh`; `--dry-run` — то же без issue, проверок, коммита и PR. С `--in-place` —
`VERSION`, заводит issue и PR в хосте; `--dry-run` — то же без issue, проверок, коммита и PR. С `--in-place` —
раскладывает копию в рабочее дерево существующего worktree хоста без git-операций (подключение хоста; PR фичи,
которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости — отказ `20`, ничего не
пишется). Обзор по трём хостам рядом — `make -C docs statements-check`; сборка снимка для контура
@ -112,7 +112,8 @@ docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka run --rm
поэтому отдельным шагом в `check-full` не гоняется. Для быстрой проверки одних миграций: `make migration-cycle`.
- `check-infra` — TCP-проверка доступности PostgreSQL и NATS из `api/.env.test`; вместо длинного трейсбека pytest
подсказывает «поднимите инфраструктуру: `make up`».
- `web-check` — `npm install`, `biome check src`, `tsc --noEmit`, `npm run build` в `web/`.
- `web-check` — `npm install`, `biome check src`, `tsc --noEmit`, `npm run build` в `web/`. Юнит-тесты и линтер фронта
в `web-check` не входят — их запускают из `web/`: `npm test` (vitest) и `npm run lint` (ESLint + Prettier).
Дополнительные цели репозиториев (не общие): `arm-core` — `migrate`, `seed`, `seed-dev`, `dbml-check`,
`migration-cycle`, `check-infra`, `docker-build`, `web-check`; `scan-gateway` — `help`, `docker-build`, `emulator`;
@ -180,7 +181,7 @@ PR убедитесь, что `docs` и `fixtures` доступны (пропу
| `make venv` | `.venv` (Python 3.11, `uv`) с `jsonschema` и `PyYAML` — зависимостями скриптов (после обновления репозитория — перезапустить) |
| `make validate-contracts` | `python scripts/validate_contracts.py`: примеры `contracts/examples/` против текущих схем JSON Schema 2020-12, примеры `contracts/examples/v1/` — против схем прежнего MAJOR `*.v1.schema.json` (окно перехода на контракт 2.0), форма упорядоченных перечней кодов `contracts/statement-code-orders.yaml` (коды — 4 цифры формы; в варианте `full` код стоит 1 + (вхождений в `repeats`) раз, в `simplified` — не чаще); код 1 при любой ошибке |
| `make validate-openspec` | `openspec validate --all --strict` — все спеки и незаархивированные change'и (один change: `make validate-openspec CHANGE=<id>`) |
| `make check` | все проверки выше и `test-snapshot`, `test-sync-web`, `test-sync-statements` |
| `make check` | все проверки выше и `test-snapshot`, `test-sync-statements` |
| `make test-sync-statements` | сценарии `sync-statements.sh` на синтетических репозиториях: коды `--check`, `--in-place`, `--dry-run`, отказ при прямой правке |
| `make sync-statements` | копия пакета `arm_statements` в хост одной командой (worktree, issue, PR); `SYNC_ARGS="--host arm-core [--check\|--dry-run]"` |
| `make statements-check` | обзор свежести копий по трём хостам рядом (`arm-core`, `tabular-parser`, `scan-gateway`): строка итога на хост; в `check` не входит |

View File

@ -36,8 +36,8 @@ Gitea в контуре нет)
- `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 вместе со своими копиями этих файлов, удалять их вручную не нужно.
(`.claude/`, `.codegraph/`, `CLAUDE.md`, `openspec/config.yaml`) вырезается на любой глубине, в том числе во вложенном
`arm-core/web/`; удалять их вручную не нужно.
- Внешний Gitea `git.aepif.ru` — ссылки вида `https://git.aepif.ru/RSHB/arm-core/pulls/19` в архивах change'ей
скрипт превращает в `RSHB/arm-core#19` (номера относятся к внешнему Gitea, в контуре их нет).
@ -64,14 +64,7 @@ docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour
Что делает скрипт (репозиторий-источник не меняется):
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` рядом не найден — предупреждение,
снимок делается.
0a. Для каждого сервиса, где в `<ref>` есть `api/arm_statements/`: проверяет свежесть копии пакета (`sync-statements.sh --check`
0. Для каждого сервиса, где в `<ref>` есть `api/arm_statements/`: проверяет свежесть копии пакета (`sync-statements.sh --check`
по дереву `<ref>`). Отстаёт — предупреждение и абзац в `SNAPSHOT.md`; копия правлена напрямую или чужая — снимок не
делается (код 1); нет `arm-statements` рядом или подходящего Python — предупреждение, снимок делается. Нет
`api/arm_statements/` — проверка молча пропускается.
@ -92,33 +85,15 @@ docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour
`.env.test`, `contracts/`). Любая проблема — код возврата 1 и список.
Тест скрипта — `make test-snapshot` (синтетический репозиторий с артефактами агента в корне и в `web/`); проверка
отставания `web/` и остановка переноса — в `make test-sync-web`, а проверка копии `arm_statements` (пропуск, отставание, правка)
копии `arm_statements` (пропуск, отставание, правка)
— в `make test-snapshot` (нужен Python ≥ 3.11 с PyYAML, `make venv`).
### 3.1. Синхронизация `arm-core/web` с `arm-web`: `scripts/sync-web.sh`
### 3.1. Фронт arm-core: `web/` ведётся в arm-core
Фронт ведётся в двух местах: отдельный репозиторий `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 или откатить, иначе автоперенос их затёр бы.
С 21.10.2026 фронт разрабатывается прямо в `arm-core/web` (PR в `RSHB/arm-core` → `main`), отдельный репозиторий
`RSHB/arm-web` заморожен на `test` `de4e477` (= arm-core `3157a15`), скрипт синхронизации `sync-web.sh` удалён
(`docs/context/00-workspace-layout.md`). Снимок arm-core берёт `web/` как есть из `<ref>`; флаг `--web-as-is`,
нужный раньше для переноса с отставанием от arm-web, игнорируется с предупреждением.
Проверено 06.10.2026: снимки arm-core@1d7f2c8, tabular-parser@3b32462, scan-gateway@dffc18a проходят `make check` без
`docs` и `fixtures` (1084 / 1087 / 726 passed, пропуски только по причинам из раздела 5).
@ -149,8 +124,8 @@ arm-web #29), поэтому снимок оставляет только нуж
`WEB_DEV_ALLOW` и повторите снимок.
**Правило обратного потока:** `web/package.json` из контура наружу не переносить — он урезан (нет eslint, prettier, vitest,
typescript и скриптов `test`/`lint`/`format`); источник истины — `arm-web`, а `arm-core/web` синхронизируется из него
(раздел 3.1). Правки зависимостей в контуре возвращаются в arm-web вручную, только по существу, а не файлом целиком.
typescript и скриптов `test`/`lint`/`format`); источник истины — `web/package.json` в arm-core `main` снаружи (раздел 3.1).
Правки зависимостей в контуре возвращаются в arm-core вручную, только по существу, а не файлом целиком.
## 4. Как положить снимок в контур
@ -280,9 +255,7 @@ git diff <коммит sync> HEAD -- . ':!SNAPSHOT.md' > /tmp/contour-arm-core.p
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 синхронизации и
«закрывай», затем повторить.
3. `prepare-contour-snapshot.sh` по каждому сервису; скрипт завершился словами «снимок готов».
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.

View File

@ -416,8 +416,8 @@ UI прячет элементы по `GET /me → permissions` (объедин
### 5.6.1 Формы ББ/ОФР после единой нормализации (arm-core `main` `35a24e7a`)
> Работает на `main` ядра: change `unified-statement-normalization` (RSHB/arm-ka-docs#116; контракт распознавания v2.0,
> DBML v5.19), части А (arm-core #129) и Б (#132). Встроенный фронт `arm-core/web` получает новые типы через `sync-web`
> после плана 6 (arm-web). Правила — `context/05-domain-rules.md` §7, §9.3, §9.5 пп. 2, 4, 9, §11; экран — спека `arm-web` того же change.
> DBML v5.19), части А (arm-core #129) и Б (#132). Фронт `arm-core/web` эти типы уже использует (план 6, сделан в
> arm-web и перенесён синхронизацией до 21.10.2026; дальше фронт ведётся в arm-core). Правила — `context/05-domain-rules.md` §7, §9.3, §9.5 пп. 2, 4, 9, §11; экран — спека `arm-web` того же change.
> В `…/statement` поля добавлены, старые не удалены: клиент, написанный по §5.6, продолжит читать ответ.
> **Исключение — карточка файла:** `FileOut.job.warnings` сменил тип со строк на объекты (ниже, «Карточка файла»), это
> несовместимо — экран файлов обновляется в той же выкладке, что и ядро. Раздел 5.6 ниже описывает базовые маршруты; где он
@ -756,7 +756,7 @@ async function download(path: string, fallbackName: string): Promise<void> {
| Задача | Что поменяется для фронта | Как подготовиться |
|---|---|---|
| OQ-54 — SSO РАИСА | Меняется только получение токена (вместо dev-формы) | держать слой получения токена изолированным (`frontend-auth-guide.md` §9) |
| arm-ka-docs#116 — единая нормализация ББ/ОФР | Выполнено в `arm-core` `main` (#129, #132) — см. §1 и §5.6.1; ждёт `arm-web` (план 6) и выкладки сервисов tabular-parser / scan-gateway на контракт 2.0 | типы из §5.6.1 |
| arm-ka-docs#116 — единая нормализация ББ/ОФР | Выполнено в `arm-core` `main` (#129, #132) — см. §1 и §5.6.1; фронт (план 6) — в `arm-core/web`; выкладка сервисов на контракт 2.0 — `qa-statements-v2.md` | типы из §5.6.1 |
Состояние этих задач — в Gitea (`RSHB/arm-core`, `RSHB/arm-ka-docs`); после слияния каждой этот раздел переезжает в §1.

View File

@ -1,8 +1,9 @@
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 3b, 4 и фронт arm-web)
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 3b, 4 и фронт)
Документ для ручного тестирования текущего `main` бэкенда и ветки `test` фронта `arm-web`. Путь проверен 05.10.2026 на
macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого каталога до экрана администратора — около 15 минут, из них
10 — скачивание образов. Сценарий в UI (раздел 8) проверен 08.10.2026 в Chrome 1600×1000 на данных РБ 2024 Q3.
Документ для ручного тестирования текущего `main` репозитория `arm-core` — бэкенда и встроенного фронта `web/` (с
21.10.2026 фронт ведётся там; прежний отдельный репозиторий `arm-web` заморожен и для тестирования не нужен). Путь
проверен 05.10.2026 на macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого каталога до экрана администратора —
около 15 минут, из них 10 — скачивание образов. Сценарий в UI (раздел 8) проверен 08.10.2026 в Chrome 1600×1000 на данных РБ 2024 Q3.
Как читать: раздел 4 — поднять стенд, раздел 8 — пройти основной путь аналитика в браузере, разделы 6–7 — то же и
глубже через API (Swagger/curl): там проверяются права, коды ошибок и граничные случаи, до которых UI не доходит.
@ -21,7 +22,7 @@ macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого ката
офис видит всё, филиал — только свои ГК.
- Импорт справочников из выгрузки ЕЦП CRM (xlsx/csv) с пробным прогоном и отчётом.
- Администрирование: пользователи, роли, матрица «роль × право», защита последнего администратора.
- Фронтенд **arm-web** (демо-прототип, раздел 8): группы компаний, консолидации и создание консолидации, панель
- Фронтенд `arm-core/web` (раздел 8): группы компаний, консолидации и создание консолидации, панель
«Организации» с чек-листом CSV, загрузка файлов ЮЛ с опросом статуса, просмотр ОСВ-документа с подтверждением, проверка
форм ББ/ОФР рядом со сканом PDF (правка, акцепт нарушений, подтверждение), сводная ОСВ с фильтрами, пробелами и
экспортом xlsx; администрирование `/admin` («Пользователи и роли»).
@ -33,7 +34,7 @@ macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого ката
`aep_3.pdf` / `aep_6.pdf` из `fixtures/ocr`.
- Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых
документов, чек-лист, сводная ОСВ, ручное сопоставление контрагента, экспорт xlsx (раздел 7; полный прогон на данных РБ и
контрольные цифры — 7.1–7.3). Основной путь консолидации есть и в UI arm-web — раздел 8.
контрольные цифры — 7.1–7.3). Основной путь консолидации есть и в UI — раздел 8.
- План счетов — полная выгрузка 1С заказчика, 488 строк (arm-core #25): субсчета реальных ОСВ (66.01, 76.07.x, забалансовые,
буквенные `01.К`) резолвятся в `account_id`. Границы входов консолидации: периметр ≤ 500 ЮЛ, комментарий статуса ≤ 1000, поиск
`q` ≤ 500 символов (arm-core #26, раздел 6.5).
@ -77,7 +78,7 @@ macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого ката
## 3. Клонирование
Репозитории должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures`, `arm-web` (в коде пути
Репозитории должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures` (в коде пути
вида `../docs/...`, `../fixtures/...`):
```bash
@ -85,11 +86,9 @@ mkdir arm-ka && cd arm-ka
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-docs.git docs
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-core.git arm-core
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-fixtures.git fixtures
git clone -b test ssh://git@git.aepif.ru:2222/RSHB/arm-web.git arm-web
```
Фронт тестируется с ветки **`test`** репозитория `arm-web` (интеграционная ветка прототипа; `main` там — исходная копия
без экранов консолидации). Обновление: `git -C arm-web pull`.
Фронт — каталог `web/` в `arm-core`, отдельно его клонировать не нужно. Обновление: `git -C arm-core pull`.
Сервисы распознавания собираются из исходников, поэтому для загрузки файлов (6.6–7, раздел 8) рядом нужен ещё
`tabular-parser`, для PDF (6.8, 8.5) — `scan-gateway`:
@ -141,28 +140,24 @@ docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --buil
curl -s localhost:8001/healthcheck # nats: ok
```
### 4.3 Фронтенд arm-web (второй терминал, из корня `arm-ka`)
### 4.3 Фронтенд (второй терминал, из корня `arm-ka`)
```bash
cd arm-web
make install # npm install; первый раз — несколько минут
make start # dev-сервер rsbuild на :3000, браузер откроется сам
cd arm-core/web
npm install # первый раз — несколько минут
REACT_APP_ROOT_PATH=/ npm run start # dev-сервер rsbuild на :3000, браузер откроется сам
```
Откроется <http://localhost:3000/> с формой «Вход для разработки». Запросы `/api` dev-сервер проксирует на ядро
(`ARM_CORE_URL`, по умолчанию `http://localhost:8000`), поэтому `REACT_APP_API_URL` задавать не нужно, а CORS не мешает
скачиванию файлов. Ядро на другом адресе: `ARM_CORE_URL=http://host:port make start`.
скачиванию файлов. Ядро на другом адресе: `ARM_CORE_URL=http://host:port REACT_APP_ROOT_PATH=/ npm run start`.
Запускайте именно `make start`: он задаёт `REACT_APP_ROOT_PATH=/`. Голый `npm run start` ждёт путь приложения как на
платформе банка (`/aurora/apps/fastapi-dkb-armka`) и показывает белый экран. Не используйте и `make build`/`npm run build`
Не забывайте `REACT_APP_ROOT_PATH=/`: без него `npm run start` ждёт путь приложения как на
платформе банка (`/aurora/apps/fastapi-dkb-armka`) и показывает белый экран. Не используйте и `npm run build`
для тестирования: в production-сборке dev-формы входа нет, страница показывает «Вход выполняется через РАИСА».
Проверка сборки фронта (линтер, типы, юнит-тесты, сборка) — `make check` в `arm-web`; ручному тестированию она не нужна.
Старый фронт `arm-core/web` (только `/admin`) по-прежнему запускается
`cd arm-core/web && npm install && REACT_APP_API_URL=http://localhost:8000 REACT_APP_ROOT_PATH=/ npm run start` — на том же
порту 3000, поэтому одновременно с arm-web не поднимайте. Он нужен, пока в arm-web не исправлены уведомления
администрирования (arm-web#7, раздел 6.1).
Проверка сборки фронта (линтер, типы, юнит-тесты, сборка) — `make web-check` в `arm-core` и `npm test`, `npm run lint` в
`arm-core/web`; ручному тестированию она не нужна.
Настройки из `api/.env.example` (`DEBUG=true`, `AUTH_DEV_ISSUER=true`, `CORS_ORIGINS=`, `FORWARDED_ALLOW_IPS=127.0.0.1`)
для локального стенда менять не нужно: `DEBUG=true` включает документацию API на `/docs`, dev-вход и разрешает `make seed-dev`.
@ -225,10 +220,10 @@ curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
### 6.1 Администрирование (UI `/admin`, вход `admin@armka.local`)
Пункт меню «Система → Пользователи и роли». **Известный дефект arm-web#7:** в arm-web на этом экране не появляются
Пункт меню «Система → Пользователи и роли». **Известный дефект arm-web#7:** на этом экране не появляются
уведомления (ни об успехе, ни об ошибке), в консоли браузера — `message.error is not a function`, модалка создания при ошибке
остаётся открытой без текста. Пока он не исправлен, сценарии с ожиданием «уведомлением» (A3, A6, A8, A10) проходите на старом
фронте `arm-core/web` (раздел 4.3) или проверяйте код ответа во вкладке Network.
остаётся открытой без текста. Пока он не исправлен, в сценариях с ожиданием «уведомлением» (A3, A6, A8, A10) проверяйте код ответа во
вкладке Network.
| № | Шаги | Ожидание |
|---|---|---|
@ -242,7 +237,7 @@ curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
| A8 | Деактивировать единственного активного админа | 409 `LAST_ADMIN` |
| A9 | Матрица прав: роли EXPERT добавить `EXPORT`, сохранить | `/me` эксперта содержит `EXPORT` |
| A10 | Матрица прав: снять `USERS_ADMIN` у ADMIN, когда других админов нет | 409 `LAST_ADMIN` |
| A11 | Войти `expert@armka.local`, открыть `/admin` напрямую | «Нет доступа» / «Раздел доступен пользователям с правом администрирования», запросов к `/admin/*` нет (вкладка Network). Пункт меню в arm-web виден всем ролям — так в прототипе, в `arm-core/web` он скрыт |
| A11 | Войти `expert@armka.local`, открыть `/admin` напрямую | «Нет доступа» / «Раздел доступен пользователям с правом администрирования», запросов к `/admin/*` нет (вкладка Network). Пункт меню виден всем ролям — известное ограничение |
| A12 | После правки своих ролей обновить страницу | профиль в шапке обновился только после перезагрузки (известное ограничение) |
### 6.2 Справочники (Swagger `/docs`)
@ -605,7 +600,7 @@ curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m js
(раздел 6.6); данные — в `../fixtures/osv/`. Токены: `ka_go` (`CONSOLIDATION_VIEW/EDIT`, видит всё), `ka_rf`
(`CONSOLIDATION_VIEW/EDIT`, только ГК Ростовского РФ), `expert` (только `CONSOLIDATION_VIEW`), `client_manager`
(`CONSOLIDATION_VIEW`, Краснодарский РФ), `admin` (прав на консолидации нет). Подробные схемы запросов и ответов, коды ошибок и
нюансы для фронта — `dev/api-contract-slice-4.md`; Swagger — `/docs`. В UI arm-web (раздел 8) есть K1 (W5–W8), загрузка в
нюансы для фронта — `dev/api-contract-slice-4.md`; Swagger — `/docs`. В UI (раздел 8) есть K1 (W5–W8), загрузка в
версию (K8, W13), чек-лист CSV (W11), свод, пробелы и экспорт (K11, K12 частично, K17 — W29–W35); блокировки, периметра,
статусов и сопоставления (K4–K7, K13–K16, K18–K20) в UI нет.
@ -776,7 +771,7 @@ Q3-only — `missing_quarters = ["Q1","Q2"]`.
привязаны, свод 1421 строка, суммы равны своду РБ 2025 Q1 из тех же файлов).
4. Карточка документа по-прежнему показывает период из шапки — это не баг. ББ/ОФР режим не затрагивает.
## 8. Сквозной сценарий в UI (arm-web)
## 8. Сквозной сценарий в UI
Основной путь аналитика по макету: группа компаний → новая консолидация → файлы ЮЛ → проверка ОСВ и форм ББ/ОФР →
сводная ОСВ. Номера в квадратных скобках — экраны макета `docs/mockup/screens/<nn>-*.png` (открывать по одному через
@ -784,7 +779,7 @@ Q3-only — `missing_quarters = ["Q1","Q2"]`.
ошибок и граничные случаи, которых нет в UI, — в разделах 6–7.
**Предусловия.** Стенд по разделу 4: ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`, `make seed-dev` (ГК «Русский
Бисквит» с ГСК из РБ и Счастливочной), фронт — `make start`. Для 8.5 (PDF) — ещё шлюз и эмулятор ИС Сканирование (из
Бисквит» с ГСК из РБ и Счастливочной), фронт — раздел 4.3. Для 8.5 (PDF) — ещё шлюз и эмулятор ИС Сканирование (из
каталога `arm-core`, как в 4.2):
```bash
@ -830,10 +825,10 @@ docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --buil
|---|---|---|
| W16 | В панели Счастливочной клик по «ОСВ 60» | экран «Просмотр ОСВ»: крошки «Отчётность › ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО › Проверка документа», подзаголовок «<имя файла> · XLS · <дата>», справа статус «Загружен», кнопка «✓ Подтвердить» неактивна; надпись «Суммы в рублях; строки ОСВ только для чтения»; поиск «Контрагент или ИНН», фильтр «Субсчёт», справа «Строк: 73»; над таблицей «Предупреждения (1)» — «Информация: иерархия строк — по группировке листа (outline)». **Плоская** таблица (arm-web #13): строка на контрагента, колонки «Субсчёт», «Контрагент», «ИНН» и шесть сумм (сальдо на начало, обороты, сальдо на конец — Дебет / Кредит). Строка «Итого по документу» внизу (сумма корневых строк счёта, как «Итого» файла): «—», «27 923 002,09», «107 554 149,33», «99 188 100,32», «—», «19 556 953,08» (сальдо нач. Кт + обороты Кт − обороты Дт = сальдо кон. Кт) |
| W17 | ka_go, через API (в UI не воспроизводится: загрузка только из панели ЮЛ внутри версии, см. раздел 8.7). Загрузить один файл из `../fixtures/osv/schastl/2024-Q3/` в QA-ЮЛ `$LE_QA` (id ЮЛ, заведённого для QA, не входящего в ГСК демо; без консолидации — маршрутом ЮЛ): `curl -s -X POST "localhost:8000/api/v1/legal-entities/$LE_QA/files" -H "$H" -F "files=@<файл>"`; дождаться `PROCESSED` (`GET /files/{file_id}`); `POST /documents/{id}/confirm` | документ `NEEDS_CHECK` («Контрагент ✗», шапка другой организации) до подтверждения; после — 200 `CONFIRMED`, `confirmed_by` заполнен; повтор — 409 `ALREADY_CONFIRMED`. Файл остаётся на стенде вне версий демо: счётчики W18 не затрагивает; `DELETE` после ручного подтверждения даёт 409; в панели [39] версии этот файл виден только по переключателю «Все документы организации» с подписью «вне консолидации» |
| W18 | Остальные файлы РБ и Счастливочной 2024 Q1–Q3 (всего 22, список — 7.1): через «Загрузить документы» у каждого ЮЛ и подтверждение каждого ОСВ, либо скриптом из каталога `arm-web`: `scripts/demo-prepare.sh <version_id>` (id — из адреса `?version=`) | скрипт печатает строку на каждый файл (пять файлов из W13 — «уже загружен», это нормально), ждёт распознавания (до 5 мин), подтверждает только неподтверждённые ОСВ (чистые уже «Загружен», автоподтверждение, OQ-157) и в конце печатает `строк: 1385 документов: 22 несопоставленных: 1236 пробелов: 7`. Статус версии — «Коррекция данных» (K9) появляется сразу после распознавания (автоподтверждение чистых документов, OQ-157), в «Организации» в колонке «ОСВ» — счета ЮЛ |
| W18 | Остальные файлы РБ и Счастливочной 2024 Q1–Q3 (всего 22, список — 7.1): через «Загрузить документы» у каждого ЮЛ и подтверждение каждого ОСВ, либо скриптом из корня `arm-ka`: `docs/scripts/demo-prepare.sh <version_id>` (id — из адреса `?version=`) | скрипт печатает строку на каждый файл (пять файлов из W13 — «уже загружен», это нормально), ждёт распознавания (до 5 мин), подтверждает только неподтверждённые ОСВ (чистые уже «Загружен», автоподтверждение, OQ-157) и в конце печатает `строк: 1385 документов: 22 несопоставленных: 1236 пробелов: 7`. Статус версии — «Коррекция данных» (K9) появляется сразу после распознавания (автоподтверждение чистых документов, OQ-157), в «Организации» в колонке «ОСВ» — счета ЮЛ |
`demo-prepare.sh` берёт пользователя вторым аргументом (по умолчанию `ka_go@armka.local`), адрес ядра — из `API`
(по умолчанию `localhost:8000/api/v1`), каталог фикстур — из `FIXTURES_DIR` (по умолчанию `fixtures` рядом с `arm-web`).
(по умолчанию `localhost:8000/api/v1`), каталог фикстур — из `FIXTURES_DIR` (по умолчанию `fixtures` рядом с `docs`).
### 8.5 Проверка формы ББ/ОФР из PDF [41], [41-02]
@ -904,11 +899,11 @@ PDF из `fixtures/ocr` — отчётность других компаний (
- Предупреждения antd в консоли браузера («Static function can not consume context…», deprecated) ни на что не влияют.
- Уведомления в `/admin` не появляются — дефект arm-web#7 (раздел 6.1).
Баг UI — issue в `RSHB/arm-web` (раздел 11): номер сценария W, скриншот, вкладка Network с запросом и ответом.
Баг UI — раздел 11: номер сценария W, скриншот, вкладка Network с запросом и ответом.
## 9. Известные ограничения (не заводить как баги)
- Справочники — только через API; в UI arm-web — администрирование и путь консолидации (раздел 8, ограничения прототипа — 8.7).
- Справочники — только через API; в UI — администрирование и путь консолидации (раздел 8, ограничения прототипа — 8.7).
- Вход без пароля, открытый `/docs`, CORS `*` — dev-режим.
- Email `*.local` через API/UI отклоняется, при этом dev-пользователи именно такие (созданы seed напрямую).
- Удалять алиасы и пустые ГСК/ГСЗ может только ADMIN (право `DIRECTORY_DELETE`).
@ -952,9 +947,9 @@ make migrate && make seed-dev
## 11. Как заводить баги
Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`, документация — `RSHB/arm-ka-docs`. Один дефект — одна issue.
Не уверены, чей баг, — откройте вкладку Network: ответ сервера не тот, что в гайде, — arm-core; ответ верный, а экран
показывает другое — arm-web.
Gitea: бэкенд, API и экраны — `RSHB/arm-core`, документация — `RSHB/arm-ka-docs`. Один дефект — одна issue.
Не уверены, бэкенд это или фронт, — откройте вкладку Network: ответ сервера не тот, что в гайде, — бэкенд; ответ верный, а
экран показывает другое — фронт (`web/`); в issue укажите, что именно.
Шаблон:
@ -967,7 +962,7 @@ Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`,
Запрос: метод, путь, тело (без токена)
Ответ: статус и тело
Лог бэкенда: строки с трейсбэком, если есть
Окружение: ОС, браузер, версии docker/node, коммиты arm-core и arm-web (`git -C arm-core rev-parse --short HEAD`)
Окружение: ОС, браузер, версии docker/node, коммит arm-core (`git -C arm-core rev-parse --short HEAD`)
Скриншот: для багов UI — экран и вкладка Network с запросом
```
@ -981,10 +976,10 @@ Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`,
| `docker compose up --wait` без списка сервисов возвращает 1 | нормально из-за `minio-init`; используйте команду со списком из 4.1 |
| `make migrate` → connection refused | PostgreSQL ещё не `healthy`; подождать и повторить |
| `make venv` не находит Python 3.11 | `uv` скачает его сам при наличии интернета; проверьте `uv python list` |
| Белый экран на `localhost:3000` | фронт запущен `npm run start` без `REACT_APP_ROOT_PATH=/`; запускайте `make start` (4.3) |
| После входа «Нет связи с сервером» или ошибки сети | ядро не запущено на `:8000`; если на другом адресе — `ARM_CORE_URL=… make start`. Для старого `arm-core/web` — забыты `REACT_APP_API_URL`/`REACT_APP_ROOT_PATH` |
| Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `make start`, а не `make build` |
| `make start`: порт 3000 занят | запущен второй фронт (например, старый `arm-core/web`); остановите его |
| Белый экран на `localhost:3000` | фронт запущен `npm run start` без `REACT_APP_ROOT_PATH=/` (4.3) |
| После входа «Нет связи с сервером» или ошибки сети | ядро не запущено на `:8000`; если на другом адресе — задайте `ARM_CORE_URL` (4.3) |
| Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `npm run start` (4.3), а не `npm run build` |
| `npm run start`: порт 3000 занят | запущен второй фронт (например, из worktree); остановите его |
| Скан PDF не отображается («Не удалось открыть PDF») | старый браузер — обновите его |
| PDF завис в «Обработка» | не запущены `scan-gateway`/`scan-emulator` (раздел 8, предусловия) |
| PDF в статусе «Ошибка» (подсказка `INVALID_DOCUMENT`) | эмулятор знает только PDF из `fixtures/ocr` с JSON-ответом (6.8); свой PDF так и должен падать |
@ -993,4 +988,4 @@ Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`,
| `/healthcheck` показывает `nats: down` | NATS не поднят; на сценарии Среза 1 не влияет, но загрузка ОСВ (6.6) без NATS не дойдёт до парсера |
| Файл завис в `UPLOADED`/`PROCESSING` | не запущен tabular-parser (`curl localhost:8001/healthcheck`) или ядро запущено без `NATS_ENABLED=true`; без ack файл через ~2 мин станет `ERROR` |
Подробнее об инфраструктуре — `docs/dev/README.md`, о бэкенде — `arm-core/README.md`, о фронте — `arm-web/README.md`.
Подробнее об инфраструктуре — `docs/dev/README.md`, о бэкенде — `arm-core/README.md`, о фронте — `arm-core/web/README.md`.

View File

@ -1,7 +1,7 @@
# Тестирование единой нормализации ББ/ОФР (контракт 2.0)
Документ для тестировщиков к выкладке «единой нормализации ББ/ОФР» на тестовый стенд в контуре банка (19.10.2026).
Проверка — через экраны АРМ КА (arm-web) на стенде контура. Как поднять стенд локально, войти и проверить API через
Проверка — через экраны АРМ КА (фронт `arm-core/web`) на стенде контура. Как поднять стенд локально, войти и проверить API через
Swagger — общий гайд [`qa-guide.md`](qa-guide.md) (разделы 4, 6.9, 8.5); здесь — только то, что изменилось с этой
выкладкой.
@ -378,7 +378,7 @@ tabular-parser `4d1709a`, scan-gateway `1d05b9e`, общий пакет `arm-sta
## 8. Как заводить баги
На стенде контура баг заводится задачей в Jira проекта AURORA, как остальные замечания тестирования. Шаблон описания —
`qa-guide.md` раздел 11; в задаче укажите, чей он, если понятно (экран — arm-web, API и данные — arm-core, распознавание
`qa-guide.md` раздел 11; в задаче укажите, чей он, если понятно (экран и API, данные — arm-core, распознавание
Excel — tabular-parser, PDF — scan-gateway; не уверены — по вкладке Network). Для форм ББ/ОФР дополнительно приложите:
1. **Файл** (или обезличенную копию) и где он лежит на стенде: ЮЛ, имя файла, дата загрузки.

View File

@ -1,7 +1,7 @@
# arm-web Specification
## Purpose
Требования к экрану проверки формы ББ/ОФР в arm-web по контракту 2.0: раскладка строк со статусами (`ok` / `detail` / `unreadable` / `missing`), подсветка «требует внимания», предупреждения у места и правка кодов строк, сумм, дат колонок и шапки.
Требования к экрану проверки формы ББ/ОФР во фронте `arm-core/web` (имя спеки — от прежнего репозитория arm-web, замороженного 21.10.2026) по контракту 2.0: раскладка строк со статусами (`ok` / `detail` / `unreadable` / `missing`), подсветка «требует внимания», предупреждения у места и правка кодов строк, сумм, дат колонок и шапки.
## Requirements

View File

@ -35,10 +35,10 @@ json dataclass-ы), шаги 2–8, общие разборы текста (дв
только скрипт, сервис читает при старте и пишет в результат как `service.statements_version`. Прямые правки копии MUST
NOT делаться.
Копию SHALL раскладывать и проверять скрипт `docs/scripts/sync-statements.sh` (по образцу `sync-web.sh`; цель `make -C
Копию SHALL раскладывать и проверять скрипт `docs/scripts/sync-statements.sh` (цель `make -C
docs sync-statements`). Источник — локальный checkout `arm-statements` рядом с workspace или `--source <dir>`, ссылка —
`origin/main` или `--ref`; хост — `--host <имя>` или `--host-dir <dir>`. Без `--check` скрипт раскладывает копию коммита
`--ref` в новый worktree хоста на ветке фичи, пишет `VERSION` и готовит issue и PR (как `sync-web.sh`). С `--in-place` скрипт SHALL раскладывать
`--ref` в новый worktree хоста на ветке фичи, пишет `VERSION` и готовит issue и PR. С `--in-place` скрипт SHALL раскладывать
копию коммита `--ref` в рабочее дерево указанного хоста (существующий worktree) без issue, коммита и PR — для
подключения хоста и для PR фичи, которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости —
отказ с кодом `20`, ничего не пишется. Копия

50
scripts/demo-prepare.sh Executable file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Подготовка демо: загрузить квартальные ОСВ РБ и Счастливочной за 2024 Q1–Q3 в версию и подтвердить ОСВ-документы.
# Использование: docs/scripts/demo-prepare.sh <version_id> [email] (стенд arm-core на localhost:8000, DEBUG=true)
# Каталог фикстур: FIXTURES_DIR (по умолчанию ../../fixtures относительно этого скрипта, т.е. workspace/fixtures); перенесён из arm-web 21.10.2026.
set -euo pipefail
V="${1:?version_id}"
EMAIL="${2:-ka_go@armka.local}"
API="${API:-localhost:8000/api/v1}"
F="${FIXTURES_DIR:-$(cd "$(dirname "$0")/../.." && pwd)/fixtures}/osv"
H="Authorization: Bearer $(curl -sf -X POST "$API/dev/token" -H 'Content-Type: application/json' -d "{\"email\":\"$EMAIL\"}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')"
members=$(curl -sf "$API/consolidations/versions/$V" -H "$H" | python3 -c '
import sys,json
for m in json.load(sys.stdin)["members"]: print(m["id"], m["inn"], m["name"])')
# ЮЛ ищем по ИНН (в справочнике имена могут быть в верхнем регистре)
le_by_inn() { echo "$members" | awk -v inn="$1" '$2 == inn { print $1; exit }'; }
LE_RB=$(le_by_inn 3528052718)
LE_SCH=$(le_by_inn 3528104476)
[ -n "$LE_RB" ] && [ -n "$LE_SCH" ] || { echo "В периметре версии нет РБ или Счастливочной:"; echo "$members"; exit 1; }
upload() { for f in "$F/$2"/2024-Q{1,2,3}/*; do
curl -s -X POST "$API/legal-entities/$1/files?consolidation_version_id=$V" -H "$H" -F "files=@$f" \
| python3 -c 'import sys,json;d=json.load(sys.stdin);print("принят" if d.get("accepted") else d.get("rejected") or d)'
done; }
upload "$LE_RB" rb
upload "$LE_SCH" schastl
echo "Жду распознавания…"
for _ in $(seq 1 60); do
pending=0
for LE in $LE_RB $LE_SCH; do
n=$(curl -sf "$API/legal-entities/$LE/files?limit=100" -H "$H" | python3 -c 'import sys,json;print(sum(f["status"] in ("UPLOADED","PROCESSING") for f in json.load(sys.stdin)["items"]))')
pending=$((pending + n))
done
[ "$pending" -eq 0 ] && break
sleep 5
done
for LE in $LE_RB $LE_SCH; do
curl -sf "$API/legal-entities/$LE/files?limit=100" -H "$H" | python3 -c '
import sys,json
for f in json.load(sys.stdin)["items"]:
for d in f["documents"]:
if d["document_type"] == "OSV" and d["status"] in ("RECOGNIZED","NEEDS_CHECK"): print(d["id"])' \
| while read -r DOC; do curl -s -o /dev/null -w "%{http_code} $DOC\n" -X POST "$API/documents/$DOC/confirm" -H "$H"; done
done
curl -sf "$API/consolidations/versions/$V/osv/summary" -H "$H" | python3 -c '
import sys,json
s=json.load(sys.stdin); print("строк:", s["rows_total"], "документов:", s["documents_total"], "несопоставленных:", s["unmapped_total"], "пробелов:", len(s["gaps"]))'

View File

@ -6,17 +6,12 @@
# Nexus (как `setup/setup.sh` шаблона), убирает цель docker-build, заменяет ссылки на внешний Gitea, пишет SNAPSHOT.md с
# провенансом и проверяет результат. Репозиторий-источник не меняется.
#
# Запуск из workspace: docs/scripts/prepare-contour-snapshot.sh [--web-as-is] <каталог репозитория> <каталог вывода> [ref]
# Запуск из workspace: docs/scripts/prepare-contour-snapshot.sh <каталог репозитория> <каталог вывода> [ref]
# ref по умолчанию — origin/main (снимок всегда с сервера, не с рабочего дерева).
# Пример: docs/scripts/prepare-contour-snapshot.sh tabular-parser /tmp/contour
# Результат: <каталог вывода>/<имя репозитория>/ — готовое дерево для rsync в клон репозитория контура.
#
# Для arm-core перед снимком проверяется, не отстаёт ли встроенный фронт web/ от RSHB/arm-web (sync-web.sh --check).
# Если отстаёт — вопрос «перенести как есть?»: «y» — снимок с отставанием (оно записывается в SNAPSHOT.md); «n» —
# запускается синхронизация (sync-web.sh: worktree, issue, PR) и перенос останавливается (код 3) до merge PR и закрытия
# issue. Без терминала вопрос не задаётся: нужен флаг --web-as-is, иначе код 3. Расхождение (прямые правки web/ в arm-core) —
# всегда стоп. arm-web ищется рядом с репозиторием (../arm-web) или в $ARM_WEB; если его нет — предупреждение, снимок делается.
# $SYNC_WEB_ARGS — дополнительные аргументы sync-web.sh (для теста: --dry-run --wt-root …).
# Флаг --web-as-is остался от синхронизации с arm-web (до 21.10.2026) и игнорируется: фронт ведётся в arm-core/web.
# Для каждого сервиса, где в $REF есть api/arm_statements, проверяется свежесть копии пакета (sync-statements.sh --check
# по дереву $REF; Р-42): отставание — предупреждение и абзац в SNAPSHOT.md, прямая правка копии — стоп (код 1), нет
# arm-statements или Python — предупреждение. Нет api/arm_statements — проверка пропускается. $SYNC_STATEMENTS_ARGS —
@ -32,9 +27,10 @@ WEB_OPTIONAL_KEEP_PREFIX="raisa-" # opt
WEB_SCRIPTS_KEEP=(build start preview) # scripts, работающие без инструментов разработки
set -euo pipefail
usage() { echo "usage: $0 [--web-as-is] <repo-dir> <out-dir> [ref]" >&2; exit 2; }
WEB_AS_IS=0
if [ "${1:-}" = "--web-as-is" ]; then WEB_AS_IS=1; shift; fi
usage() { echo "usage: $0 <repo-dir> <out-dir> [ref]" >&2; exit 2; }
if [ "${1:-}" = "--web-as-is" ]; then
echo "предупреждение: --web-as-is больше не нужен (фронт ведётся в arm-core/web) — флаг игнорируется" >&2; shift
fi
[ $# -ge 2 ] || usage
REPO=$(cd "$1" && pwd)
@ -53,42 +49,6 @@ if [ -e "$DEST" ]; then
exit 2
fi
# --- 0. arm-core: встроенный фронт web/ не должен отставать от arm-web -----------------------------------------------
WEB_LAG=""
if [ "$NAME" = arm-core ]; then
SYNC_WEB="$(cd "$(dirname "$0")" && pwd)/sync-web.sh"
ARM_WEB_DIR=${ARM_WEB:-$(dirname "$REPO")/arm-web}
# shellcheck disable=SC2206
SYNC_ARGS=(--arm-core "$REPO" --arm-web "$ARM_WEB_DIR" --core-ref "$REF" ${SYNC_WEB_ARGS:-})
if [ ! -e "$ARM_WEB_DIR/.git" ]; then
echo "предупреждение: arm-web не найден ($ARM_WEB_DIR) — отставание web/ от arm-web не проверено" >&2
else
rc=0; "$SYNC_WEB" --check "${SYNC_ARGS[@]}" || rc=$?
case "$rc" in
0) ;;
10)
if [ "$WEB_AS_IS" = 1 ]; then
answer=y
elif [ -t 0 ] || [ -p /dev/stdin ] || [ -f /dev/stdin ]; then
read -r -p "web/ в arm-core отстаёт от arm-web. Перенести в контур как есть? [y/N] " answer || answer=n
else
answer=n
echo "нет терминала и нет --web-as-is: перенос как есть не подтверждён" >&2
fi
case "$answer" in
y|Y|д|Д) WEB_LAG="отстаёт от arm-web (перенесён как есть по решению оператора, см. sync-web.sh --check)" ;;
*)
echo "запускается синхронизация arm-core/web с arm-web (sync-web.sh)…" >&2
"$SYNC_WEB" "${SYNC_ARGS[@]}" || true
echo "перенос в контур остановлен до полного завершения синхронизации: merge PR и закрытие issue (чеклист «закрывай»), затем повторите снимок." >&2
exit 3 ;;
esac ;;
20) echo "ошибка: web/ в arm-core расходится с arm-web (прямые правки) — снимок не делается, разберитесь вручную" >&2; exit 1 ;;
*) echo "ошибка: sync-web.sh --check завершился с кодом $rc" >&2; exit 1 ;;
esac
fi
fi
# --- 0a. Копия пакета arm_statements в api/arm_statements не должна отставать от arm-statements ---------------------------
# Проверяется дерево $REF хоста (git archive api → временный каталог), не рабочее. Нет api/arm_statements в $REF — пакет в
# хост ещё не подключён, проверка молча пропускается. Коды sync-statements.sh --check: 0 — тихо; 10 — предупреждение в
@ -134,8 +94,8 @@ git -C "$REPO" archive "$REF" | tar -x -C "$DEST"
# ../docs и ../fixtures; openspec CLI в контуре нет, specs/ и design.md читаются без него); docs/superpowers/ — планы и
# ledger (в них локальные пути); Dockerfile/.dockerignore — локальная сборка образа, платформа собирает образ своим CI.
# openspec/specs/ и openspec/changes/archive/ остаются: это спецификация проекта и решения (design.md).
# Артефакты агента вырезаются на любой глубине: фронт перенесён в arm-core/web/ из arm-web со своими .claude/, CLAUDE.md,
# openspec/config.yaml, docs/superpowers/ и .codegraph/. Dockerfile/.dockerignore — только в корне (образ сервиса).
# Артефакты агента вырезаются на любой глубине (вложенный проект вроде arm-core/web/ может нести свои .claude/, CLAUDE.md,
# openspec/config.yaml, docs/superpowers/ и .codegraph/). Dockerfile/.dockerignore — только в корне (образ сервиса).
rm -rf "$DEST/Dockerfile" "$DEST/.dockerignore"
find "$DEST" \( \( -type d \( -name .claude -o -name .codegraph -o -path '*/docs/superpowers' \) \) \
-o \( -type f \( -name CLAUDE.md -o -path '*/openspec/config.yaml' \) \) \) -prune -exec rm -rf {} +
@ -297,9 +257,6 @@ fi
if [ -n "$STATEMENTS_LAG" ]; then
printf '\nКопия пакета arm_statements (`api/arm_statements/`) на момент снимка требует внимания: %s\n' "$STATEMENTS_LAG" >> "$DEST/SNAPSHOT.md"
fi
if [ -n "$WEB_LAG" ]; then
printf '\nВстроенный фронт \`web/\` на момент снимка %s.\n' "$WEB_LAG" >> "$DEST/SNAPSHOT.md"
fi
# --- 5. Проверки ----------------------------------------------------------------------------------------------------
fail=0

View File

@ -1,221 +0,0 @@
#!/usr/bin/env bash
# Синхронизация встроенного фронта arm-core/web с отдельным репозиторием фронта RSHB/arm-web (ветка test).
# Делает одной командой то, что делалось вручную в arm-core #94 и #100: worktree, issue, перенос набора файлов,
# проверки фронта, коммит, PR. Гайд: dev/contour-transfer-guide.md §3.1.
#
# Точка синхронизации не хранится: это коммит arm-web@<ref>, у которого хэш дерева `src` равен хэшу `web/src`
# в arm-core@<core-ref>. Если такого коммита нет — arm-core/web правили напрямую, мимо arm-web; перенос затёр бы
# эту работу, поэтому скрипт останавливается (код 20).
#
# Набор синхронизации — всё дерево arm-web, кроме служебного (контекст агента, спеки UI, Makefile и скрипты
# демо-стенда): CLAUDE.md, .claude/, openspec/, docs/, scripts/, Makefile, ARCHITECTURE.md, .gitignore, package-lock.json.
#
# Запуск из workspace: docs/scripts/sync-web.sh [--check] [--dry-run] [--ref <ref>] [--core-ref <ref>]
# [--arm-web <dir>] [--arm-core <dir>] [--wt-root <dir>]
# --check только сравнить: 0 — синхронно, 10 — arm-core/web отстаёт, 20 — расхождение (правки напрямую)
# --dry-run worktree, перенос и контроль идентичности без issue, PR, push и проверок npm (посмотреть, что перенесётся)
# --ref ветка arm-web, по умолчанию origin/test; --core-ref — база arm-core, по умолчанию origin/main
# WEB_NODE_MODULES — откуда взять node_modules для проверок (по умолчанию <wt-root>/web-proto/arm-web/node_modules);
# если каталога нет — npm install в worktree
# Скрипт пишет только в новый worktree arm-core на ветке фичи; ничего не мержит, не удаляет и не закрывает —
# закрытие после merge идёт обычным чеклистом «закрывай».
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
# Корень workspace — где рядом лежат arm-core и arm-web. Скрипт запускают и из docs/ (корень = ../..), и из worktree
# .wt/<задача>/docs (корень = ../../../..); внешний кандидат проверяется первым — там основные checkout'ы.
WS="$(cd "$HERE/../.." && pwd)"
for cand in "$HERE/../../../.." "$HERE/../.."; do
if [ -e "$cand/arm-core/.git" ] && [ -e "$cand/arm-web/.git" ]; then WS="$(cd "$cand" && pwd)"; break; fi
done
ARM_WEB="$WS/arm-web"
ARM_CORE="$WS/arm-core"
WT_ROOT="$WS/.wt"
REF=origin/test
CORE_REF=origin/main
MODE=sync
CHECK=0
while [ $# -gt 0 ]; do
case "$1" in
--check) CHECK=1 ;; # имеет приоритет над --dry-run независимо от порядка (пайплайн добавляет $SYNC_WEB_ARGS в конец)
--dry-run) MODE=dry ;;
--ref) REF=$2; shift ;;
--core-ref) CORE_REF=$2; shift ;;
--arm-web) ARM_WEB=$2; shift ;;
--arm-core) ARM_CORE=$2; shift ;;
--wt-root) WT_ROOT=$2; shift ;;
-h|--help) sed -n '2,24p' "$0"; exit 0 ;;
*) echo "sync-web: неизвестный аргумент $1" >&2; exit 2 ;;
esac
shift
done
[ "$CHECK" = 0 ] || MODE=check
# Исключения набора синхронизации: pathspec для git diff и имена для diff -rq
EXCLUDE_NAMES=(CLAUDE.md .claude openspec docs scripts Makefile ARCHITECTURE.md .gitignore package-lock.json)
PATHSPEC=(.)
DIFF_EXCLUDES=(--exclude=node_modules --exclude=dist --exclude=.codegraph --exclude=.superpowers --exclude=.bounded)
for n in "${EXCLUDE_NAMES[@]}"; do PATHSPEC+=(":(exclude,top)$n"); DIFF_EXCLUDES+=("--exclude=$n"); done
die() { echo "sync-web: $1" >&2; exit "${2:-1}"; } # die <сообщение> [код]
[ -d "$ARM_WEB/.git" ] || [ -f "$ARM_WEB/.git" ] || die "arm-web не найден: $ARM_WEB (--arm-web <dir>)" 2
[ -d "$ARM_CORE/.git" ] || [ -f "$ARM_CORE/.git" ] || die "arm-core не найден: $ARM_CORE (--arm-core <dir>)" 2
ARM_WEB=$(cd "$ARM_WEB" && pwd); ARM_CORE=$(cd "$ARM_CORE" && pwd)
git -C "$ARM_WEB" fetch -q origin
git -C "$ARM_CORE" fetch -q origin
git -C "$ARM_WEB" rev-parse -q --verify "$REF^{commit}" >/dev/null || die "в arm-web нет $REF" 2
git -C "$ARM_CORE" rev-parse -q --verify "$CORE_REF^{commit}" >/dev/null || die "в arm-core нет $CORE_REF" 2
# --- 1. Точка синхронизации -------------------------------------------------------------------------------------------
CORE_SRC=$(git -C "$ARM_CORE" rev-parse -q --verify "$CORE_REF:web/src") || die "в arm-core@$CORE_REF нет web/src" 2
HEAD_SHA=$(git -C "$ARM_WEB" rev-parse "$REF")
HEAD7=${HEAD_SHA:0:7}
POINT=""
while IFS= read -r c; do
t=$(git -C "$ARM_WEB" rev-parse -q --verify "$c:src" 2>/dev/null || true)
if [ "$t" = "$CORE_SRC" ]; then POINT=$c; break; fi
done < <(git -C "$ARM_WEB" rev-list "$REF")
if [ -z "$POINT" ]; then
echo "sync-web: расхождение — ни у одного коммита arm-web@$REF дерево src не совпадает с arm-core@$CORE_REF:web/src." >&2
echo "arm-core/web правили напрямую, мимо arm-web; автоперенос затёр бы эти правки. Коммиты arm-core по web/:" >&2
git -C "$ARM_CORE" log --format=' %h %s' -10 "$CORE_REF" -- web/ >&2
echo "Разберитесь вручную: перенесите прямые правки в arm-web (или откатите их), затем повторите." >&2
exit 20
fi
if [ "$POINT" = "$HEAD_SHA" ]; then
echo "sync-web: arm-core@$CORE_REF web/ синхронен с arm-web@$REF ($HEAD7) — переносить нечего"
exit 0
fi
COMMITS=$(git -C "$ARM_WEB" log --format='%h %s' --no-merges "$POINT..$REF")
N_COMMITS=$(printf '%s\n' "$COMMITS" | grep -c . || true)
FILES=$(git -C "$ARM_WEB" diff --name-only "$POINT" "$REF" -- "${PATHSPEC[@]}")
N_FILES=$(printf '%s\n' "$FILES" | grep -c . || true)
echo "sync-web: arm-core@$CORE_REF web/ отстаёт от arm-web@$REF ($HEAD7)."
echo "точка синхронизации: arm-web ${POINT:0:7}; непереносённых коммитов: $N_COMMITS, файлов в наборе: $N_FILES"
printf '%s\n' "$COMMITS" | sed 's/^/ /'
[ "$MODE" != check ] || exit 10
# --- 2. Worktree -----------------------------------------------------------------------------------------------------
TMP_BRANCH="tmp/web-sync-$HEAD7"
WT="$WT_ROOT/web-sync-$HEAD7/arm-core"
[ ! -e "$WT" ] || die "worktree уже существует: $WT — завершите или удалите предыдущую синхронизацию" 2
git -C "$ARM_CORE" rev-parse -q --verify "$TMP_BRANCH" >/dev/null && die "ветка $TMP_BRANCH уже есть в arm-core" 2
mkdir -p "$(dirname "$WT")"
git -C "$ARM_CORE" worktree add -q -b "$TMP_BRANCH" "$WT" "$CORE_REF"
touch "$WT/.bounded"
echo "worktree: $WT ($TMP_BRANCH от $CORE_REF)"
# --- 3. Issue и ветка (не в dry-run) ---------------------------------------------------------------------------------
# tea 0.16+ принимает --login/--repo только после подкоманды: tea issues create --login … (arm-ka-docs#110)
TEA_FLAGS=(--login home --repo RSHB/arm-core)
ISSUE=""
BRANCH="$TMP_BRANCH"
if [ "$MODE" = sync ]; then
TITLE="web: синхронизация встроенного фронта с arm-web@${REF#origin/} ($HEAD7)"
BODY="Встроенный фронт \`arm-core/web\` отстаёт от \`RSHB/arm-web@${REF#origin/}\` ($HEAD7); точка синхронизации — arm-web ${POINT:0:7}.
Непереносённые коммиты arm-web ($N_COMMITS):
$(printf '%s\n' "$COMMITS" | sed 's/^/- /')
Bounded-задача, выполнена \`docs/scripts/sync-web.sh\`: перенос набора синхронизации ($N_FILES файлов) в \`web/\`,
проверки vitest / tsc / biome / build, PR в main. Ревью перенесённого кода против бэкенда — за исполнителем."
ISSUE_URL=$(tea issues create "${TEA_FLAGS[@]}" --title "$TITLE" --description "$BODY" | grep -o 'https://[^ ]*/issues/[0-9]*' | tail -1)
ISSUE=${ISSUE_URL##*/}
[ -n "$ISSUE" ] || die "не удалось создать issue через tea"
BRANCH="feat/$ISSUE-web-sync-$HEAD7"
git -C "$WT" branch -m "$TMP_BRANCH" "$BRANCH"
echo "issue: $ISSUE_URL; ветка: $BRANCH"
fi
# --- 4. Перенос набора и контроль идентичности ------------------------------------------------------------------------
git -C "$ARM_WEB" diff --binary "$POINT" "$REF" -- "${PATHSPEC[@]}" | git -C "$WT" apply --directory=web
REF_TREE=$(mktemp -d); trap 'rm -rf "$REF_TREE"' EXIT
git -C "$ARM_WEB" archive "$REF" | tar -x -C "$REF_TREE"
if ! diff -rq "${DIFF_EXCLUDES[@]}" "$REF_TREE" "$WT/web" >/dev/null; then
echo "sync-web: после переноса web/ не совпадает с arm-web@$REF:" >&2
diff -rq "${DIFF_EXCLUDES[@]}" "$REF_TREE" "$WT/web" >&2 || true
exit 1
fi
echo "перенесено: $N_FILES файлов; web/ идентичен arm-web@$REF по набору синхронизации"
if [ "$MODE" = dry ]; then
echo "dry-run: issue, проверки, коммит и PR не выполнялись. Посмотреть: git -C $WT status; убрать:"
echo " git -C $ARM_CORE worktree remove --force $WT && git -C $ARM_CORE branch -D $TMP_BRANCH"
exit 0
fi
# --- 5. Проверки фронта ---------------------------------------------------------------------------------------------
NM=${WEB_NODE_MODULES:-$WT_ROOT/web-proto/arm-web/node_modules}
if [ -d "$NM" ]; then
ln -s "$NM" "$WT/web/node_modules"
echo "node_modules: симлинк на $NM"
else
echo "node_modules: $NM нет — npm install в worktree"
(cd "$WT/web" && npm install --no-audit --no-fund >/dev/null)
fi
run_check() { # имя, команда... — печатает хвост вывода при ошибке и останавливает скрипт
local name=$1; shift
local log; log=$(mktemp)
if (cd "$WT/web" && "$@") >"$log" 2>&1; then rm -f "$log"; return 0; fi
echo "sync-web: проверка «$name» не прошла; worktree оставлен для разбора: $WT" >&2
tail -40 "$log" >&2; rm -f "$log"; exit 1
}
run_check vitest npx vitest run
TESTS=$(cd "$WT/web" && npx vitest run 2>&1 | grep -E '^\s*Tests\s' | sed 's/^ *//' || true)
run_check tsc npx tsc --noEmit
biome_warnings() { (cd "$WT/web" && npx biome check "$1" 2>&1 | grep -oE 'Found [0-9]+ warning' | grep -oE '[0-9]+') || echo 0; }
W_MAIN=$(biome_warnings "$ARM_CORE/web/src")
W_NEW=$(biome_warnings src)
[ "$W_NEW" -le "$W_MAIN" ] || { echo "sync-web: biome — предупреждений стало больше: $W_NEW против $W_MAIN на $CORE_REF; worktree: $WT" >&2; exit 1; }
run_check build npm run build
echo "проверки: $TESTS; tsc чисто; biome $W_NEW предупреждений (на $CORE_REF — $W_MAIN); build ок"
# --- 6. Коммит, push, PR -------------------------------------------------------------------------------------------
# web/dist игнорируется .gitignore arm-core; исключать его pathspec'ом нельзя — git 2.50 на явно названный
# игнорируемый путь отвечает «paths are ignored» и кодом 1 (arm-ka-docs#110). Симлинк node_modules .gitignore не ловит.
git -C "$WT" add -A web -- ':!web/node_modules'
git -C "$WT" -c commit.gpgsign=false commit -q -F - <<EOF
fix(web): синхронизация встроенного фронта с arm-web@${REF#origin/} ($HEAD7) (#$ISSUE)
Перенесён диапазон arm-web ${POINT:0:7}..$HEAD7 ($N_COMMITS коммитов, $N_FILES файлов набора
синхронизации) скриптом docs/scripts/sync-web.sh. После переноса web/ и
arm-web@${REF#origin/} идентичны по набору. Коммиты arm-web:
$(printf '%s\n' "$COMMITS" | sed 's/^/- /')
Co-Authored-By: sync-web.sh <noreply@aepif.ru>
EOF
COMMIT=$(git -C "$WT" rev-parse --short=7 HEAD)
git -C "$WT" push -q -u origin "$BRANCH"
PR_BODY="Закрывает #$ISSUE.
Встроенный фронт \`arm-core/web\` отставал от \`RSHB/arm-web@${REF#origin/}\` ($HEAD7). Диапазон arm-web ${POINT:0:7}..$HEAD7
перенесён скриптом \`docs/scripts/sync-web.sh\` без правок; после переноса \`web/\` и arm-web идентичны по набору синхронизации.
Коммиты arm-web ($N_COMMITS):
$(printf '%s\n' "$COMMITS" | sed 's/^/- /')
Файлы ($N_FILES):
$(printf '%s\n' "$FILES" | sed 's/^/- web\//')
Проверки в worktree: $TESTS; \`tsc --noEmit\` чисто; \`biome check src\` — $W_NEW предупреждений (на ${CORE_REF#origin/} — $W_MAIN); \`rsbuild build\` собирается.
## Rulings I made
- R-1 — служебные файлы arm-web (CLAUDE.md, .claude/, openspec/, docs/, scripts/, Makefile, ARCHITECTURE.md, .gitignore, lockfile) не переносятся: UI-спеки и демо-стенд живут в arm-web.
- R-2 — bounded-задача без OpenSpec change: перенос готового, проревьюенного в arm-web кода.
Ревью перенесённого кода против бэкенда — за исполнителем, находки с маркерами в комментариях к PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)"
PR_URL=$(tea pulls create "${TEA_FLAGS[@]}" --head "$BRANCH" --base "${CORE_REF#origin/}" --title "fix(web): синхронизация встроенного фронта с arm-web@${REF#origin/} ($HEAD7) (#$ISSUE)" \
--description "$PR_BODY" | grep -o 'https://[^ ]*/pulls/[0-9]*' | tail -1)
echo
echo "готово: коммит $COMMIT, ветка $BRANCH"
echo "issue: $ISSUE_URL"
echo "PR: ${PR_URL:-создать вручную: tea pulls create ${TEA_FLAGS[*]} --head $BRANCH --base ${CORE_REF#origin/}}"
echo "исполнителю: проверить перенесённый код против бэкенда (коммиты arm-web выше), затем — merge и «закрывай»."
echo "worktree $WT, симлинк web/node_modules и .bounded остаются до закрытия."

View File

@ -22,7 +22,7 @@ for p in .claude/skills/a/SKILL.md CLAUDE.md openspec/config.yaml docs/superpowe
put "$p" "/Users/someone/projects"
done
put openspec/specs/osv/spec.md
# вложенный фронт (как web/ из arm-web): те же артефакты на второй глубине
# вложенный проект (как фронт arm-core/web/): те же артефакты на второй глубине
for p in web/.claude/commands/opsx/apply.md web/.codegraph/.gitignore web/CLAUDE.md web/openspec/config.yaml \
web/docs/superpowers/plans/2026-10-08-demo.md; do
put "$p" "/Users/someone/projects"

View File

@ -1,135 +0,0 @@
#!/usr/bin/env bash
# Тест sync-web.sh на синтетических репозиториях arm-web и arm-core (bare origin + клон), без tea, npm и сети.
# Сценарии: синхронно; arm-core отстаёт (--check и --dry-run); arm-core правили напрямую (отказ);
# пайплайн переноса в контур (prepare-contour-snapshot.sh спрашивает / останавливается / идёт «как есть»).
# Запуск: make test-sync-web (или напрямую из корня docs).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
SYNC="$HERE/sync-web.sh"
SNAPSHOT="$HERE/prepare-contour-snapshot.sh"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t
fail=0
check() { if [ "$1" = 0 ]; then echo "ok: $2"; else echo "FAIL: $2" >&2; fail=1; fi; }
put() { mkdir -p "$(dirname "$1")"; printf '%s\n' "${2:-x}" > "$1"; }
commit_push() { git -C "$1" add -A; git -C "$1" commit -qm "$2"; git -C "$1" push -q origin "HEAD:$3" 2>/dev/null; git -C "$1" fetch -q origin; }
# --- синтетический arm-web: ветка test ---------------------------------------------------------------------------
WEB="$WORK/arm-web"
git init -q --bare "$WORK/arm-web.git"
git clone -q "$WORK/arm-web.git" "$WEB" 2>/dev/null
git -C "$WEB" checkout -q -b test
put "$WEB/src/a.ts" "export const a = 1;"
put "$WEB/README.md" "arm-web v1"
put "$WEB/package.json" '{"name":"web","devDependencies":{"@rsbuild/core":"1","@rsbuild/plugin-react":"1","tailwindcss":"3","daisyui":"4"}}'
for p in CLAUDE.md .claude/skills/s/SKILL.md openspec/config.yaml openspec/specs/ui/spec.md docs/superpowers/plans/p.md \
scripts/demo-prepare.sh Makefile ARCHITECTURE.md .gitignore; do put "$WEB/$p" "/Users/someone/arm-web"; done
commit_push "$WEB" "web v1" test
POINT=$(git -C "$WEB" rev-parse HEAD)
# --- синтетический arm-core: web/ = снимок arm-web v1 без служебных файлов, плюс платформенные файлы ----------------
CORE="$WORK/arm-core"
git init -q --bare "$WORK/arm-core.git"
git clone -q "$WORK/arm-core.git" "$CORE" 2>/dev/null
git -C "$CORE" checkout -q -b main
put "$CORE/web/src/a.ts" "export const a = 1;"
put "$CORE/web/README.md" "arm-web v1"
put "$CORE/web/package.json" '{"name":"web","devDependencies":{"@rsbuild/core":"1","@rsbuild/plugin-react":"1","tailwindcss":"3","daisyui":"4"}}'
printf 'venv:\n\tuv venv api/.venv\n' > "$CORE/Makefile"
for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test \
api/contracts/schema.json; do put "$CORE/$p"; done
commit_push "$CORE" "core v1" main
COMMON=(--arm-web "$WEB" --arm-core "$CORE" --wt-root "$WORK/wt")
# --- 1. синхронно ---------------------------------------------------------------------------------------------------
rc=0; "$SYNC" --check "${COMMON[@]}" >/dev/null || rc=$?
check "$([ "$rc" = 0 ] && echo 0 || echo 1)" "синхронно: --check возвращает 0 (rc=$rc)"
rc=0; out=$("$SYNC" --dry-run "${COMMON[@]}") || rc=$?
check "$([ "$rc" = 0 ] && [ ! -d "$WORK/wt" ] && echo 0 || echo 1)" "синхронно: --dry-run ничего не делает (rc=$rc)"
# --- arm-web уходит вперёд: правка src, новый файл, README; служебные файлы тоже меняются ---------------------------
put "$WEB/src/a.ts" "export const a = 2;"
put "$WEB/src/b.ts" "export const b = 1;"
put "$WEB/README.md" "arm-web v2"
put "$WEB/CLAUDE.md" "/Users/someone/arm-web v2"
put "$WEB/scripts/demo-prepare.sh" "v2"
commit_push "$WEB" "web v2" test
HEAD7=$(git -C "$WEB" rev-parse --short=7 HEAD)
# --- 2. отстаёт: --check ----------------------------------------------------------------------------------------------
rc=0; out=$("$SYNC" --check "${COMMON[@]}" 2>&1) || rc=$?
check "$([ "$rc" = 10 ] && echo 0 || echo 1)" "отстаёт: --check возвращает 10 (rc=$rc)"
check "$(grep -q "web v2" <<<"$out" && echo 0 || echo 1)" "отстаёт: --check перечисляет непереносённые коммиты"
check "$(grep -q "${POINT:0:7}" <<<"$out" && echo 0 || echo 1)" "отстаёт: --check называет точку синхронизации"
# --- 3. отстаёт: --dry-run переносит набор в worktree -------------------------------------------------------------------
rc=0; "$SYNC" --dry-run "${COMMON[@]}" >/dev/null || rc=$?
WT="$WORK/wt/web-sync-$HEAD7/arm-core"
check "$([ "$rc" = 0 ] && [ -d "$WT" ] && echo 0 || echo 1)" "dry-run: worktree $WT создан (rc=$rc)"
check "$([ "$(git -C "$WT" rev-parse --abbrev-ref HEAD)" = "tmp/web-sync-$HEAD7" ] && echo 0 || echo 1)" "dry-run: временная ветка tmp/web-sync-$HEAD7"
check "$([ -f "$WT/.bounded" ] && echo 0 || echo 1)" "dry-run: .bounded создан"
check "$([ "$(cat "$WT/web/src/a.ts")" = "export const a = 2;" ] && echo 0 || echo 1)" "dry-run: изменённый src/a.ts перенесён"
check "$([ -f "$WT/web/src/b.ts" ] && echo 0 || echo 1)" "dry-run: новый src/b.ts перенесён"
check "$([ "$(cat "$WT/web/README.md")" = "arm-web v2" ] && echo 0 || echo 1)" "dry-run: README перенесён"
check "$([ ! -e "$WT/web/CLAUDE.md" ] && [ ! -e "$WT/web/scripts" ] && echo 0 || echo 1)" "dry-run: служебные файлы arm-web не перенесены"
git -C "$WT" add -A web >/dev/null
TREE_WT=$(git -C "$WT" write-tree)
check "$([ "$(git -C "$WT" rev-parse "$TREE_WT:web/src")" = "$(git -C "$WEB" rev-parse test:src)" ] && echo 0 || echo 1)" "dry-run: дерево web/src совпадает с arm-web test:src"
git -C "$WT" reset -q
rc=0; "$SYNC" --dry-run "${COMMON[@]}" >/dev/null 2>&1 || rc=$?
check "$([ "$rc" != 0 ] && echo 0 || echo 1)" "dry-run повторно: отказ, worktree уже существует (rc=$rc)"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
# --- 4. пайплайн переноса: prepare-contour-snapshot.sh видит отставание ---------------------------------------------
# сети в тесте нет: проверку сборки web/ в снимке отключаем (она покрыта test_prepare_contour_snapshot.sh)
export SNAPSHOT_SKIP_WEB_BUILD=1
export ARM_WEB="$WEB" SYNC_WEB_ARGS="--dry-run --wt-root $WORK/wt"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out1" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 3 ] && echo 0 || echo 1)" "пайплайн без терминала: перенос остановлен, rc=3 (rc=$rc)"
check "$(grep -qi "остановлен" <<<"$out" && echo 0 || echo 1)" "пайплайн: сообщение «перенос остановлен до завершения синхронизации»"
check "$([ -d "$WT" ] && [ -f "$WT/web/src/b.ts" ] && echo 0 || echo 1)" "пайплайн: синхронизация запущена, worktree создан"
check "$([ ! -d "$WORK/out1/arm-core" ] && echo 0 || echo 1)" "пайплайн: снимок не создан"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
rc=0; out=$(printf 'y\n' | "$SNAPSHOT" "$CORE" "$WORK/out2" origin/main 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && [ -f "$WORK/out2/arm-core/web/src/a.ts" ] && [ ! -e "$WORK/out2/arm-core/web/src/b.ts" ] && echo 0 || echo 1)" \
"пайплайн, ответ «y»: снимок как есть (rc=$rc)"
rc=0; out=$(printf 'n\n' | "$SNAPSHOT" "$CORE" "$WORK/out3" origin/main 2>&1) || rc=$?
check "$([ "$rc" = 3 ] && [ -d "$WT" ] && echo 0 || echo 1)" "пайплайн, ответ «n»: синхронизация запущена, перенос остановлен (rc=$rc)"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
rc=0; out=$("$SNAPSHOT" --web-as-is "$CORE" "$WORK/out4" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && [ -f "$WORK/out4/arm-core/SNAPSHOT.md" ] && echo 0 || echo 1)" "пайплайн, --web-as-is: снимок без вопроса (rc=$rc)"
check "$(grep -q "отстаёт" "$WORK/out4/arm-core/SNAPSHOT.md" && echo 0 || echo 1)" "пайплайн, --web-as-is: отставание записано в SNAPSHOT.md"
unset SYNC_WEB_ARGS
# arm-web рядом нет (в тесте он есть как сосед $CORE, поэтому путь задаётся явно несуществующим)
export ARM_WEB="$WORK/no-such-arm-web"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out5" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && grep -q "arm-web не найден" <<<"$out" && echo 0 || echo 1)" "пайплайн без arm-web рядом: предупреждение, снимок сделан (rc=$rc)"
unset ARM_WEB
# --- 5. arm-core правили напрямую: точки синхронизации нет ----------------------------------------------------------
put "$CORE/web/src/a.ts" "export const a = 'direct';"
commit_push "$CORE" "core: прямая правка web" main
rc=0; out=$("$SYNC" --check "${COMMON[@]}" 2>&1) || rc=$?
check "$([ "$rc" = 20 ] && echo 0 || echo 1)" "прямая правка arm-core: --check возвращает 20 (rc=$rc)"
check "$(grep -q "прямая правка web" <<<"$out" && echo 0 || echo 1)" "прямая правка arm-core: названы коммиты arm-core по web/"
export ARM_WEB="$WEB"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out6" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" != 0 ] && [ ! -d "$WORK/out6/arm-core" ] && echo 0 || echo 1)" "пайплайн при расхождении: снимок не делается (rc=$rc)"
unset ARM_WEB
# --- 6. вызовы tea: флаги после подкоманды ---------------------------------------------------------------------------
# tea 0.16 не принимает --login/--repo перед подкомандой («flag provided but not defined: -login», arm-ka-docs#110);
# сам tea тест не вызывает (нет сети), поэтому проверяется текст скриптов
bad=$(grep -nE '(^|[^[:alnum:]_-])tea[[:space:]]+-' "$SYNC" "$SNAPSHOT" || true)
check "$([ -z "$bad" ] && echo 0 || echo 1)" "tea: флаги идут после подкоманды${bad:+ — нарушения: $bad}"
[ "$fail" = 0 ] || exit 1
echo "ok: sync-web.sh — точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса"

View File

@ -5,7 +5,7 @@
экранов приведены демонстрационные данные группы компаний «Русский Бисквит»; названия организаций, даты и суммы в вашем
интерфейсе могут отличаться.
Инструкция описывает фронт `arm-web` (ветка `test`) и ядро `arm-core` (`main`) по состоянию на 20.10.2026. Как поднять
Инструкция описывает АРМ КА из `arm-core` (`main`: ядро и встроенный фронт `web/`) по состоянию на 20.10.2026. Как поднять
стенд, какие учётные записи есть и как заводить дефекты — в гайде тестировщика
[`../dev/qa-guide.md`](../dev/qa-guide.md).
@ -801,5 +801,5 @@ USERS_ADMIN», роли не меняются.
- Документ в статусе «Ошибка» — распознавание не удалось; для PDF на локальном стенде это ожидаемо для файлов вне
тестового набора.
- Кнопка «Загрузить документы» неактивна — версия не «в работе» (раздел 7.1).
- Дефект — issue в Gitea: экраны — `RSHB/arm-web`, ответы сервера — `RSHB/arm-core`. Шаблон и как понять, чей баг, —
- Дефект — issue в Gitea `RSHB/arm-core` (экраны и ответы сервера). Шаблон и как понять, чей баг, —
`qa-guide.md`, раздел 11. Приложите скриншот экрана и запрос с ответом из вкладки Network.