diff --git a/docs/architecture/plan.md b/docs/architecture/plan.md new file mode 100644 index 0000000..9675251 --- /dev/null +++ b/docs/architecture/plan.md @@ -0,0 +1,673 @@ +# План: Сервис M2M (НРД) — связующий модуль брокера, Реестр российского ПО + +## Краткий итог на одной странице + +``` +┌──────────────────────────────────────────────────────────────────────────────┐ +│ ЧТО ДЕЛАЕМ: Внутренний модуль брокера для M2M-перевода ЦБ через НРД (MOST) │ +│ КОМУ: сначала — нашему заказчику; потом — тиражируется другим брокерам │ +│ СТЕК: Go (ядро) + Java (crypto-service на КриптоПро JCP) + React (admin-ui) │ +│ ОС/СУБД: РЕД ОС 7.3 (целевая) + PostgreSQL Pro Certified │ +│ ДЕПЛОЙ: одна ВМ внутри контура, docker-compose (без k8s) │ +│ КАНАЛ К НРД: Интеграционный шлюз (REST) — основной; ONYX — резерв │ +│ ВНЕШНИЕ: ЛК ESIA Finance (через эмулятор сейчас); Fansy → fansy-store │ +│ КРИПТО: КриптоПро CSP/JCP, класс СКЗИ КС1; подпись операторов — серверная │ +│ SLA: 5 мин/этап (старт) → 2 мин/этап (цель); таймаут MOST: 10→5 мин │ +│ ДЕДЛАЙН ПРОДА: 01.09.2026 (124-ФЗ + Указание ЦБ) │ +│ РЕЕСТР ПО: ПП-1236, заявка на M5; стек и архитектура — соответствуют │ +└──────────────────────────────────────────────────────────────────────────────┘ +``` + +## Архитектура одной картинкой + +``` + ВМ внутри контура брокера (Astra Linux SE) + ┌─────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌──────────────┐ REST (ESIA Finance API V1) ┌────────────────────┐ │ + │ │ lk-emulator │ ─────────────────────────────► │ lk-gateway │ │ + │ │ (Go + HTMX) │ ◄─ callback статусов ────────── │ (Go) │ │ + │ └──────────────┘ └─────────┬──────────┘ │ + │ (заглушка ЛК клиента, пока ЛК ESIA Finance не готов) │ │ + │ ▼ │ + │ ┌─────────────────────┐ │ + │ admin-ui ◄─── WebSocket / REST ───────────────│ m2m-core │ │ + │ (React) │ FSM + SLA-метрики │ │ + │ • журнал │ идемпотентность │ │ + │ • ручное согласование │ по GUID │ │ + │ • сертификаты └────┬─────────┬──────┘ │ + │ • уведомления │ │ │ + │ • SLA-дашборд ▼ ▼ │ + │ ┌──────────┐ ┌──────────┐ │ + │ │ fansy- │ │ notify │ │ + │ │ store │ │ (Go) │ │ + │ │ (PG Pro) │ │ plugins: │ │ + │ │ ◄─ ETL │ │ smtp, │ │ + │ │ команда │ │ ya360, │ │ + │ │ Fansy │ │ webhook, │ │ + │ └──────────┘ │ ws-push │ │ + │ └──────────┘ │ + │ ▲ │ + │ gRPC по UDS │ │ + │ ┌──────────────────────┐ ┌──────────┴──────────┐ │ + │ │ crypto-service │ ◄────────────────│ nsd-adapter │ │ + │ │ Java + КриптоПро JCP │ │ (Go) │ │ + │ │ • XMLDSig подпись │ │ ┌──────────────────┐ │ │ + │ │ • проверка входящих │ │ │ ИШ REST основ. │ │ │ + │ │ • подпись операторов │ │ │ WS ONYX резерв │ │ │ + │ │ (КС1) │ │ │ ФШ fallback│ │ │ + │ └──────────────────────┘ │ └────────┬─────────┘ │ │ + │ └──────────┼───────────┘ │ + │ │ │ + └───────────────────────────────────────────────────────┼────────────────────┘ + │ HTTPS / mTLS + ▼ + ┌────────────────────────┐ + │ ИШ НРД (на нашей ВМ) │ + │ REST: /api/package/… │ + └───────────┬────────────┘ + │ ЭДО НРД (подпись делает ИШ) + ▼ + ┌────────────────────────┐ + │ Сервис MOEX MOST / │ + │ WS ONYX gost-*.nsd.ru │ + └────────────────────────┘ +``` + +Хранилища на ВМ: **PostgreSQL Pro Certified** (`m2m-core`, `fansy-store`, аудит, конфиги), **MinIO** (эталоны подписанных XML). + +## Поток одной заявки end-to-end (донор) + +``` +ИНВЕСТОР ЛК (ESIA lk-gateway m2m-core fansy-store crypto-svc nsd-adapter ИШ НРД → MOST → Брокер 2 + Finance/ + эмулятор) + │ │ │ │ │ │ │ │ │ │ + │ заявление │ │ │ │ │ │ │ │ │ + ├──подпись──►│ │ │ │ │ │ │ │ │ + │ │ POST /claims │ │ │ │ │ │ │ │ + │ ├─────────────►│ │ │ │ │ │ │ │ + │ │ │ verify sig │ │ │ │ │ │ │ + │ │ ├───────────────────────────────────────►│ │ │ │ │ + │ │ │ enqueue │ │ │ │ │ │ │ + │ │ ├───────────►│ │ │ │ │ │ │ + │ │ │ │ enrich │ │ │ │ │ │ + │ │ │ ├────────────►│ │ │ │ │ │ + │ │ │ │ build XML │ │ │ │ │ │ + │ │ │ │ M2MTransferRequest │ │ │ │ │ + │ │ │ ├──── send via ИШ ─────────────────────►│ │ │ │ + │ │ │ │ (ИШ сам подписывает пакет ЭДО) │ │ │ + │ │ │ │ │ │ ├──#M2MTR──►│ │ │ + │ │ │ │ │ │ │ квитанция ЭДО │ │ + │ │ │ │ │ │ │◄──────────┤ │ │ + │ │ │ │ WAIT (≤10→5 min) │ │ │ → Брокер 2 │ + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ #M2MTD (подтверждение от Брокера 2) │ + │ │ │ │ │ │ │◄──────────┤◄───────┤◄────────┤ + │ │ │ │ verify sig +│ │ │ │ │ │ + │ │ │ │ apply Decision │ │ │ │ │ + │ │ │ ├ → notify (e-mail + Yandex Messenger + WS) │ │ │ + │ │ callback │ │ │ │ │ │ │ │ + │ │◄─────────────┤ │ │ │ │ │ │ │ + │ статус ОК │ │ │ │ │ │ │ │ │ + │◄───────────┤ │ │ │ │ │ │ │ │ + │ │ │ │ ждём SUB16 (черновики 16-х поручений) для депозитария │ │ + │ │ │ │ │ │ │◄── #SUB16 ─────────────────── │ + │ │ │ │ → передать в депозитарную систему / показать в admin-ui │ │ + + Ветка TIMEOUT: если Decision не пришёл за 10→5 мин — MOST сам шлёт Transfer_reject (#SUBER) → FSM в Rejected. +``` + +## Roadmap M1–M5 (визуально) + +``` + 2026 + нед: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 + ├───────────────┤ + M1: │ Каркас+стенды│ + │ • моно-репо │ + │ • XSD→Go │ + │ • ИШ на ВМ │ + │ • crypto-svc │ + │ • контракт ЛК │ + │ • DDL fansy │ + ├───────────────────────┤ + M2: │ Сквозной донор TEST3 │ + │ • lk-emulator v1 │ + │ • FSM донор │ + │ • #M2MTR/#M2MTD │ + │ • метрики SLA │ + ├───────────────────────┤ + M3: │ Реципиент+admin-ui v1 │ + │ • Decision исх │ + │ • Handbook+Participant│ + │ • SUB16, SUBBR/SUBER │ + │ • Справка о расходах │ + │ • роли, сертификаты │ + ├───────────────┤ + M4: │ Ручное+notify │ + │ • manual-appr │ + │ • smtp+ya360 │ + │ • plug-ins │ + │ • совм.с Fansy│ + ├───────────────────────┤ + M5: │ ИБ+нагруз+Реестр+ПРОД │ + │ • 57580 аудит │ + │ • k6 SLA 2 мин │ + │ • заявка в Реестр ПО │ + │ • ПСИ на промконтуре │ + └───── 01.09.2026 ──────┘ + ДЕДЛАЙН (124-ФЗ) +``` + +## Маппинг сообщений и каналов (одна таблица) + +| Этап | Тип пакета НРД | Кто формирует | Кто подписывает | Канал | +|------|----------------|---------------|------------------|--------| +| Заявление от инвестора | (внутренний JSON ESIA Finance) | ЛК клиента / эмулятор | ЛК | REST → `lk-gateway` | +| Запрос на перевод M2M | `#M2MTR` (M2MTransferRequest) | `m2m-core` | **ИШ НРД** | ИШ REST | +| Тех.ответ НРД на запрос | `#M2MER` (M2MTransferResponse) | НРД | НРД | ИШ → `nsd-adapter` | +| Решение принимающего | `#M2MTD` (M2MTransferDecision) | Брокер 2 / мы (если реципиент) | ИШ | ИШ | +| Авто-отказ по таймауту | `#SUBER` (Transfer_reject) | **MOST автоматически** | НРД | ИШ → нам | +| Черновики 16-х поручений | `#SUB16` | НРД | НРД | ИШ → нам → депозитарий | +| Справка о расходах | `Assets_investment_account_transfer_details` | Брокер 1 | ИШ | ИШ | +| Статус справки | `Assets_investment_details_status` | Брокер 2 | ИШ | ИШ | +| Действие оператора в admin-ui | внутр. подпись | оператор | **`crypto-service` серверной ЭП** | внутри ВМ | + +## Готовность по Реестру российского ПО (ПП-1236) — матрица + +``` +Критерий ПП-1236 Статус +───────────────────────────────────────────────────── ────────────────── +1. Российский правообладатель, инокапитал ≤ 50% ⚠ проверить ЕГРЮЛ +2. Исключительное право — у правообладателя ⚠ договоры с разрабами на M1 +3. Свободная гражданская реализация в РФ ✓ ОК +4. Выплаты за рубеж < 30% выручки ✓ нет иностр. лицензий +5. Не содержит гостайны ✓ ОК +6. Класс ПО (02.13 СЭД + 02.09 финсектор) ⚠ уточнить на M5 +7. Исходники и сборки в РФ ✓ git.zetit.ru +8. Техподдержка в РФ ✓ M5 +9. Документация на русском ✓ закладываем с M1 +10. Совместимость с реестровой ОС (Astra/РЕД) ✓ M2 (CI на Astra) + +СТЕК (всё в Реестре или open-source upstream): + ОС: РЕД ОС 7.3 (целевая) ✓ + СУБД: PostgreSQL Pro Certified ✓ + JDK: Liberica / Axiom JDK ✓ + СКЗИ: КриптоПро CSP/JCP, КС1 ✓ + Уведомления: Yandex 360 + smtp + WS ✓ + Прочее: Go, MinIO, NATS — open-source ✓ +``` + +## Старт работ — первая неделя M1 + +**Репозиторий**: `https://git.zetit.ru/zuevav/Bridge-and-Join-s` (РФ-хостинг, основной). + +**Среда разработки — выделенная ВМ заказчика (dev-стенд)** на целевой ОС (Astra Linux SE 1.7+ или РЕД ОС 7.3). Решение: разрабатываем сразу на «живой» машине, чтобы не было разрыва между dev и прод-средой, и чтобы КриптоПро/ИШ/тестовые сертификаты НРД жили в одном защищённом контуре. На эту ВМ ставится **Claude Code CLI**, разработка идёт там же. + +### Подготовка dev-ВМ (день 1–2, до старта Трека А) + +| Что | Зачем | +|------|-------| +| ОС: **РЕД ОС 7.3** | Целевая ОС. На dev-этапе — бесплатная редакция РЕД ОС («Свободная»), без сертификата ФСТЭК; одну сертифицированную лицензию закупаем к M3-M4 для целевого stage-стенда | +| Учётные записи: `dev` (без sudo по умолчанию), `admin` (sudo) | Минимизация прав; разработка под `dev`, обслуживание — под `admin` | +| Сетевая сегментация: dev-ВМ в отдельном VLAN с доступом наружу только к whitelist (git.zetit.ru, gost-*.nsd.ru, rsa-*.nsd.ru, registry.npmjs.org/proxy, proxy.golang.org/proxy, anthropic.com для Claude Code) | Ограничение поверхности, контроль исходящего трафика | +| Прокси / NAT с журналированием | Аудит исходящих соединений (требование 57580) | +| Установлено: Go 1.23+, JDK 21 (Liberica), Docker/Podman, docker-compose, Git, Make, jq, xmlstarlet | Базовый dev-набор | +| Установлено: КриптоПро CSP + JCP (когда лицензия будет получена) | Для `crypto-service` | +| Установлено: ИШ НРД (когда дистрибутив получим) | Для интеграционного канала | +| Установлено: **Claude Code CLI** | Разработка в репо, доступ к gpu/контурам не требуется | +| Доступ: git.zetit.ru — SSH-ключ `dev` пользователя; push в основной репо | Чтобы коммиты шли напрямую | +| Доступ: тестовые сертификаты GUEST/TEST3 | Работа со стендами НРД | +| Бэкап: БД и `/etc` ежедневно на отдельный том | Безопасность работы | +| Ресурсы dev-ВМ: **8 vCPU / 16 GB RAM / 200 GB SSD** | Запас на полный стек: Go-сервисы, Postgres, MinIO, JVM (crypto-service + ИШ НРД), admin-ui dev-server, Claude Code, CI-раннер на той же ВМ, e2e-тесты с Playwright (Chromium), параллельная сборка/линтер, место под логи и эталонные XML в MinIO. Прод-ВМ — отдельно, конфигурируется к M3-M4. | + +### Безопасность работы Claude Code на «живой» машине + +- **Запуск Claude Code только под `dev` (без sudo).** Если нужно sudo (установка пакетов) — отдельная сессия `admin` вручную, не через Claude Code. +- **Ограничения файловой системы**: рабочая директория = клон репо в `/srv/dev/Bridge-and-Join-s`. Нет доступа к `/home/admin`, `/etc/cryptopro`, контейнерам ключей. +- **Закрытые ключи КриптоПро и сертификаты** хранятся в отдельной директории `/var/cryptopro/keys/`, владелец `crypto:crypto`, mode `0700`, доступ только из `crypto-service` через JCP. Claude Code и dev-пользователь туда не ходят. +- **`.claude/settings.json` в репо**: разрешённые директории — только корень репо; запрет команд `rm -rf`, `dd`, доступ к `/etc/`, `/var/cryptopro/`, `/etc/ipsec.d/`. +- **CI и сборки** — отдельный раннер, не на dev-ВМ. Прод-сборки и подпись релизов — на изолированной build-ВМ. +- **Журналирование действий**: shell history `dev` пользователя пишется с timestamp; журнал работы Claude Code сохраняется в `~/.claude/logs/` и регулярно архивируется. +- **Сетевой трафик Claude Code наружу** (anthropic.com) согласован с ИБ заказчика; если сегмент закрытый — Claude Code запускается только в момент когда нужен (не всегда), либо через корпоративный исходящий прокси с логированием. +- **Никаких ПДн в репо** (пример заявления для эмулятора — синтетические данные). + +Параллельно три трека: + +**Трек А — Код (наша команда, день 1–5)** +- Проинициализировать структуру моно-репо: `cmd/`, `internal/{m2m-core,nsd-adapter,lk-gateway,lk-emulator,notify,fansy-store}/`, `services/crypto-service/` (Java), `web/admin-ui/`, `docs/`, `deploy/docker-compose/`, `migrations/`. +- `go.mod`, `Makefile`, `.golangci.yml`, базовый CI на `git.zetit.ru` (lint + test, раннеры — на ВМ заказчика). +- Сгенерировать **Go-модели из XSD** (`DOC/M2MSchemas_260408/`) с (de)serializer windows-1251, кастомным `NSDDateTimeType` и валидатором pattern (ReferenceId `M2M[A-Z0-9]{13}`, ISIN, ИНН, DeponentCode, IIAContractType `T12|T03`). +- Round-trip тесты на эталонах `DOC/Эталонные сообщения/*.xml` и `DOC/Примеры/*.xml`. +- README на русском. + +**Трек Б — Контракты для смежных команд (день 1–7)** +- **DDL `fansy-store` v0** в `docs/fansy-contract/v1/ddl/` + `data-dictionary.md` + `etl-requirements.md` → отправить команде Fansy на согласование. Это разблокирует их ETL. +- **OpenAPI `lk-gateway` v0** по контракту ESIA Finance API V1 (`/api/v1/back_office/claims/...`) → отправить команде ЛК как точку синхронизации. + +**Трек В — Заказчик, фоном 2–4 недели** +- Запросить у НРД дистрибутив **Интеграционного шлюза** + ставить на тестовую ВМ. +- Инициировать выпуск **тестовых сертификатов GUEST и TEST3** (ГОСТ + RSA). +- Получить лицензию и дистрибутив **КриптоПро CSP + JCP** под РЕД ОС 7.3 (целевая). +- Юр-проверки для Реестра ПО: ЕГРЮЛ правообладателя (доля иноучастия ≤ 50%), договоры с разработчиками на отчуждение исключительного права. + +**Definition of Done первой недели** +- [ ] Структура репо в `git.zetit.ru/zuevav/Bridge-and-Join-s` инициализирована, CI зелёный. +- [ ] Все 6 типов M2M-сообщений десериализуются/сериализуются в windows-1251 без потерь, эталоны проходят. +- [ ] DDL `fansy-store` v0 отправлен команде Fansy. +- [ ] OpenAPI-контракт `lk-gateway` отправлен команде ЛК ESIA Finance. +- [ ] Заявки заказчика поданы: ИШ, сертификаты GUEST/TEST3, КриптоПро, юр-аудит. + +## Контекст + +Репозиторий — green-field (только документация в `DOC/`). Задача — построить **внутренний связующий модуль брокера** для обмена с **НРД** сообщениями о переводе ценных бумаг (M2M, ЭДО НРД). + +Существующий ландшафт у заказчика: + +- **ЛК клиента — уже есть.** Инвестор сам формирует и **подписывает заявление через внутреннюю подсистему ЛК**. Наш сервис получает уже подписанное заявление через **API ЛК клиента**. Свой фронт инвестора не делаем. +- **Fansy (учётная система депозитария)** — система-источник по счетам, реквизитам, остаткам. **Сам ETL Fansy → БД делает ДРУГАЯ команда разработки в автоматизированном режиме.** Наша зона: спроектировать принимающую БД (схема, индексы, миграции, исходя из требований документации НРД к данным M2M), согласовать контракт с командой Fansy и **передать им перечень и структуру наших таблиц**, **читать и анализировать** эти данные. Сам процесс выгрузки в наш скоуп не входит. +- **Договор ЭДО с НРД** подписан, **лицензии КриптоПро / VipNet** есть. +- **Стек**: Go (бэкенд) + Java-sidecar для XMLDSig по ГОСТ (см. ниже). +- **Развёртывание**: **одна ВМ внутри контура компании** (не k8s; docker-compose или systemd-юниты). +- **Цель — Реестр российского ПО** (ПП РФ № 1236). + +Поток (`Схема M2M от НРД.pdf`): инвестор инициирует и подписывает в ЛК → ЛК передаёт заявление в наш сервис → мы валидируем, обогащаем из Fansy, формируем `M2MTransferRequest`, подписываем и шлём в НРД → принимаем `M2MTransferResponse` (тех. ответ) → ждём `M2MTransferDecision` от встречного брокера (или сами формируем, если мы сторона-реципиент) → закрываем сделку, сообщаем ЛК и депозитарию. + +## SLA и регуляторные сроки + +- **Стартовое SLA**: ≤ **5 минут** на каждый этап (приём от ЛК, обогащение из `fansy-store`, отправка в НРД, обработка ответа, обработка Decision, передача в ЛК/депозитарий). +- **Целевое SLA**: ≤ **2 минуты** на этап без переписывания архитектуры — за счёт прогрева кэшей справочников, тёплого пула SOAP/HTTP-соединений, event-driven обработки, индексов БД. +- **Регуляторный таймаут MOST «нет ответа от Брокера 2»**: на старте — **10 минут**, целевой — **5 минут**. По истечении сервис MOST формирует автоматический `Transfer_reject.xml` (тип пакета `SUBER`) — это делает MOST, мы только отслеживаем тайминг и обрабатываем входящий отказ как штатную ветку FSM. +- **Регуляторный дедлайн прод-релиза — 01.09.2026.** Вступают в силу **Федеральный закон № 124-ФЗ от 23.05.2025** и Указание Банка России о проведении операций по зачислению ЦБ на счёт депо без поручения депонента (изменения п.5 ст.7 39-ФЗ «О рынке ценных бумаг»). На этот срок закладываем M5. + +Проектные следствия: +- **Очередь на каждый этап** (in-memory worker pool на старте → внешняя шина при росте) с метриками длительности этапа (Prometheus histograms `m2m_stage_duration_seconds`). +- **SLA-budget alerting**: при > 80% бюджета — нотификация оператору. +- **Идемпотентность по GUID** на всех точках, чтобы повторы (ретраи в SLA-окне) не порождали дубли. +- Сначала **синхронная in-process архитектура** (быстрее всего и проще держит 2 минуты), внешняя шина — только если этапы выйдут на разные ВМ. + +## Криптография: Go + ГОСТ — пересмотренный план + +В Go нет зрелой реализации XMLDSig по ГОСТ Р 34.10-2012 / 34.11-2012, поэтому криптография изолирована в отдельный сервис **`crypto-service`** на Java + Apache Santuario с ГОСТ-патчем. gRPC через Unix Domain Socket (минимальная задержка — критично для SLA). + +**Уточнение по результатам ре-аудита.** Основной канал отправки — **ИШ через REST API**, и **ИШ сам подписывает пакет ЭДО** на стороне НРД-шного софта. Поэтому для основного потока внешний XMLDSig нам не нужен. `crypto-service` остаётся обязательным для: + +- проверки подписи **входящих** квитанций ЭДО, ответов НРД, сообщений от брокеров; +- **подписи действий оператора** в ручном согласовании (серверная подпись от имени оператора); +- **резервного канала через WS ONYX напрямую** — там подпись/упаковка пакета целиком на нашей стороне; +- криптографических проверок целостности эталонных архивов в MinIO. + +**Решено: используем КриптоПро.** В `crypto-service` — **КриптоПро JCP** на JVM, **КриптоПро CSP** на ВМ. Архитектурно сохраняем `Provider`-абстракцию (Валидата / VipNet / иное), чтобы при тиражировании другой компании можно было подключить иной провайдер без правки бизнес-логики, но реализация на M1 — только КриптоПро. Для проверки совместимости с эндпоинтами НРД (исторически ГОСТ-контуры МосБиржи — Валидата) — отдельная задача проверки на стенде GUEST/TEST3 в M1. + +## Целевая архитектура (один моно-репозиторий, одна ВМ) + +| # | Модуль | Стек | Назначение | +|---|--------|------|-----------| +| 1 | `lk-gateway` | **Go** (chi) | REST-интеграция с **ЛК клиента** на платформе **ESIA Finance** (`/api/v1/back_office/...`): получение заявок на M2M-перевод (`/claims/`), реквизитов (`/requisites`), пользователей (`/users`), сообщений (`/messages`), сертификатов (`/control_certificates`), Депо-счетов (`/depo_accounts`); проверка подписи заявления через `crypto-service`; callback статусов обратно в ЛК. Basic HTTP, кодировка UTF-8 — по контракту ESIA Finance API V1. | +| 1a | `lk-emulator` | **Go** + минимальный HTML/HTMX | **Эмулятор ЛК клиента** (временная заглушка). Веб-форма «загрузить заявление» с моделированием контракта **ESIA Finance API V1** (`POST /claims/`, статусные апдейты): оператор тестирования «как будто загрузил» заявление и нажимает «Отправить» — это запускает в нашей системе полный путь обработки документа. Используется на стендах GUEST/TEST3 и в регресс-сьюите. Когда реальный ЛК будет готов — эмулятор остаётся как тестовый инструмент в QA-окружении. | +| 2 | `m2m-core` | **Go** | FSM сделки, идемпотентность по GUID, валидация против XSD, **ручное согласование** как параллельная ветка автомата, метрики SLA | +| 3 | `fansy-store` | **PostgreSQL Pro** + Go-репозиторий | **Только принимающая БД** под выгрузку Fansy (схема и миграции — наши). Сам ETL делает другая команда. Внутри нашей системы — Go-репозиторий с типизированным API «получи реквизиты по ИНН/коду депонента», локальный кэш в памяти | +| 4 | `nsd-adapter` | **Go** | Три транспорта к НРД (см. раздел «Транспорт к НРД»): **(1) ИШ REST API — основной**, **(2) WS ONYX напрямую — резерв**, **(3) обменные папки ИШ — fallback**. Сериализация/парсинг XML в **windows-1251**; кастомный `NSDDateTimeType`; маршрутизация по типам пакетов (`#M2MTR`/`#M2MTD`/`#M2MER`/`SUBBR`/`SUBER`/`SUB16`/Справки/квитанции ЭДО) | +| 5 | `crypto-service` | **Java 21 (Liberica JDK) + КриптоПро JCP** (CSP на ВМ) | XMLDSig подпись/проверка (ГОСТ и RSA), gRPC по UDS. Для основного канала ИШ REST подпись пакета делает сам ИШ; здесь — проверка входящих, подпись действий оператора, резервный канал WS ONYX. Архитектурно сохранена `Provider`-абстракция (на случай добавления Валидата CSP / VipNet в будущем) | +| 6 | `notify` | **Go** | E-mail (SMTP внутренний) + **Yandex Messenger** (Yandex 360 для бизнеса, до-интеграция готового бота заказчика) + WS-push в `admin-ui`. **Архитектура — провайдеры-плагины**: единый интерфейс `Notifier`, реализации (smtp, yandex360, telegram, mattermost, webhook…), подключение и переключение через конфиг и UI без правки кода | +| 7 | `admin-ui` | React (SPA) + Go BFF в `lk-gateway` | Веб-интерфейс администратора и сотрудника депозитария: журнал приходов/уходов, статусы, ручное согласование, SLA-дашборд, **управление сертификатами для подписи** (загрузка/обновление/отзыв серверных ключей и сертификатов оператора и системного ключа НРД) | +| 8 | Хранилища | **PostgreSQL Pro Certified** (на той же ВМ), MinIO (локальный, для эталонных подписанных XML) | Состояние сделок, аудит, копия выгрузки Fansy | + +Шина сообщений на старте — **встроенная in-process** (Go channels + worker pool); если выйдем за пределы одной ВМ или разнесём этапы — добавляем NATS JetStream (легче Kafka, в реестре через `Tarantool/NATS-форки` нет, но как open source ставится). + +## Поток обработки одного заявления (целевой ≤ 2 мин) + +1. **Приём от ЛК** (`lk-gateway`): REST POST с подписанным XML заявления → проверка подписи (`crypto-service`) → запись `incoming_request` в БД, ack ЛК. +2. **Валидация и обогащение** (`m2m-core` + `fansy-connector`): валидация по XSD, разрешение ИНН и кодов депонента из локальной копии Fansy. +3. **Формирование `M2MTransferRequest`** (`m2m-core`): сборка по схеме, GUID = идемпотентный ключ. +4. **Подпись** (`crypto-service`): XMLDSig по ГОСТ или RSA в зависимости от профиля. +5. **Отправка в НРД** (`nsd-adapter`): SOAP к ONYX, разбор `M2MTransferResponse` (INFO/ERROR). +6. **Ожидание `Decision`** (если мы — донор): входящий poll или callback от НРД, разбор Confirmation/Rejection. +7. **Формирование `Decision`** (если мы — реципиент): получение подтверждения сотрудника депозитария (авто или ручное согласование) → подпись → отправка. +8. **Финализация**: запись результата, callback в ЛК клиента, событие в `notify`, обновление статуса в `admin-ui`. + +Каждый этап измеряется: `stage_started_at`, `stage_finished_at` → метрика `m2m_stage_duration_seconds{stage="..."}`. Алерт при превышении 80% SLA-бюджета. + +## Ручное согласование + +В FSM сделки добавлена ветка **manual-approval** на каждом критичном этапе (`AwaitingFansyEnrichment`, `AwaitingDecision`, `AwaitingNSDSend`): + +- В `admin-ui` сотрудник депозитария видит сделку «на ручном согласовании» с кнопками `Подтвердить` / `Отклонить с кодом` / `Доработать` (с комментарием). +- Триггеры ручного режима: + - явный флаг в заявлении из ЛК (для теста/спецсценариев), + - правила (сумма, ISIN из white-list, нерезидент и т.п.) — конфигурируемые, + - таймаут авто-обработки (защитный fallback). +- **Подпись действий оператора — серверная, от имени оператора** (через `crypto-service` на серверном ГОСТ-ключе, выделенном на оператора). Решение принято ради SLA: подпись на рабочей станции через КриптоПро CSP добавляет 10–30 секунд на каждое действие плюс зависимость от состояния АРМ — это съедает бюджет 2 минут. Серверная подпись даёт стабильные миллисекунды. +- Требования к серверной подписи оператора (документируем в формуляре СКЗИ и регламенте): + - **Двухфакторная авторизация оператора** в `admin-ui` перед любым действием подписи (пароль + TOTP/корпоративный SSO). + - Контейнер ключа оператора на сервере защищён, доступ через HSM или закрытый раздел КриптоПро; ключ не извлекаем. + - Каждая операция подписи сопровождается **аудит-записью**: оператор, действие, IP/браузер, GUID сделки, timestamp, хэш подписанного XML — неизменяемая (append-only таблица + периодический хэш-сцепленный snapshot). + - Регламент отзыва ключа оператора при увольнении/смене роли. +- Документ «ОП личного кабинета оператора» (правила использования, ответственность) подключим к проекту позже, как только заказчик его передаст. + +## Управление сертификатами через `admin-ui` + +В веб-интерфейсе администратор имеет раздел «Сертификаты», в котором происходит **только обновление сертификатов для подписи** (без управления самими ключами на низком уровне). Закрытые ключи остаются в защищённом серверном хранилище (КриптоПро, HSM) и через UI **не выгружаются и не вводятся вручную** — UI работает только с открытой частью / привязкой. + +Что делает раздел: + +- Просмотр текущих сертификатов: системный (для подписи M2M в НРД, профили GUEST/TEST3/PROD ГОСТ и RSA), серверные сертификаты операторов. +- Срок действия, серийный номер, отпечаток, издатель, fingerprint — отображаются для контроля. +- **Загрузка нового сертификата** (`.cer`/`.crt`) для существующего ключевого контейнера — кнопка «Обновить сертификат», файл валидируется (срок действия, цепочка доверия, соответствие открытому ключу контейнера) и подменяет старый без рестарта сервиса. +- Привязка сертификата к роли (системный/оператор `<имя>`/конкретный профиль НРД). +- **Алерты об истечении** срока (за 30/14/7/1 день) — в `notify` (e-mail + Yandex Messenger) и в шапке `admin-ui`. +- Полный аудит-лог операций с сертификатами (кто, когда, какой fingerprint обновил). + +Ограничения и безопасность: + +- Доступ к разделу — только для роли `Администратор СКЗИ` (отдельная роль, не равна обычному админу). +- Все операции — под двухфакторной авторизацией. +- Регламент обновления оформляется как часть документа «Эксплуатация СКЗИ». + +## Уведомления для депозитария (для соблюдения SLA) + +Уведомления по двум каналам параллельно (`notify`): + +1. **E-mail** — обязательный (внутренний SMTP). +2. **Yandex Messenger (Yandex 360 для бизнеса)** — мгновенная доставка в корпоративный мессенджер. У заказчика **уже есть готовый бот**, мы его до-интегрируем: передаём API-токен / webhook / список chat-id и user-id через конфиг и UI настроек. Российский сервис — важно для Реестра ПО. +3. **Push в `admin-ui`** через WebSocket — мгновенно, если оператор открыл вкладку (бесплатно, без внешних сервисов). + +### Расширяемость провайдеров (важное требование тиражирования) + +Сейчас сервис делается под заказчика (Yandex 360 + e-mail). Но другая компания, разворачивающая систему у себя, должна иметь возможность **легко переключиться на свои каналы** без правок кода. Поэтому: + +- В коде — единый интерфейс `Notifier { Send(ctx, recipient, template, data) }`. +- Провайдеры — отдельные пакеты-плагины: `smtp`, `yandex360`, `telegram`, `mattermost`, `rocketchat`, `webhook` (универсальный POST по URL заказчика). +- Список активных провайдеров и их параметры — в конфиге (YAML/ENV) и в `admin-ui` в разделе «Уведомления»: можно включить/выключить, ввести токены, тестово отправить сообщение, настроить роутинг по ролям и типам событий. +- Шаблоны сообщений — версионируемые, на русском, с подстановками (`{{guid}}`, `{{stage}}`, `{{deadline}}`). +- Документация для интегратора: «как подключить новый канал» — описание интерфейса и пример провайдера. + +Логика роутинга: критичный этап (ручное согласование, > 80% SLA, отказ НРД) → параллельно e-mail + Yandex Messenger + WS-push. Обычные события — только e-mail. Маршрутизация по ролям (администратор, сотрудник депозитария, оператор) — настраивается в `admin-ui`. + +## Соответствие схемам НРД (привязка к XSD) + +Из `DOC/M2MSchemas_260408/` (namespace `http://nsd.ru/schemas/m2m/...`, version `2026-04-08`): + +- Сообщения: `M2MTransferRequest`, `M2MTransferResponse` (`StatusCode ∈ {INFO, ERROR}`), `M2MTransferDecision`, `M2MTransferHandbook(+Request)`, `M2MTransferParticipantForm`. +- **Кодировка XML — windows-1251**, обязательный учёт в (de)serializer. +- `NSDDateTimeType` — pattern `[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\(МСК([+-][0-9]{1,2})?\)` — **только зона МСК** с опциональным сдвигом (например, `2026-03-02T14:30:45(МСК+2)`). +- `IsM2M` фиксировано `true`. +- Choice-типы: `CostInfo (Yes{Code} | No)`, `Quantity (Whole | Fractional)`, `SecurityDetails (ISIN | SecurityInfo)`, `SecurityInfo.IdentificationDetails (RegNumber | FundShares)`, `DecisionTransfer (Confirmation | Rejection)`. +- **Точные ограничения** (использовать в валидаторе): + - `ReferenceIdType`: длина 16, pattern `M2M[A-Z0-9]{13}`. + - `DeponentCodeType`: до 12 символов, `[A-Z0-9]*`. + - `ISINtype`: длина 12, `[A-Z]{2}[A-Z0-9]{9}[0-9]`. + - `OrganizationINNType`: ровно 10 цифр (юрлицо). + - `UUIDType`: стандартный UUID-pattern. + - `Decimal16`: до 38 цифр всего, до 16 после запятой. + - `IsolationStatus`: единственное значение `SGDN`. + - `SecurityClassificationEnum`: `BOND | SHAR | MFUN`. + - `SecurityCategoryEnum`: `ORDN | PREF | UKWN`. + - **`IIAContractTypeEnum`: `T12 | T03`** (T12 — обмен ИИС-1 ↔ ИИС-2, T03 — ИИС-3). Не путать с T01/T02. + - `IdentityDocumentCodeEnum`: 01–14, 21–27, 91 (полный список см. в `M2MTypesNSD.xsd`). + - `StatusCodeEnum`: `INFO | ERROR`. +- Эталоны из `DOC/Эталонные сообщения/` — fixtures для приёмочных тестов через `xml-exc-c14n`. + +## Транспорт к НРД (три канала, рекомендация — ИШ REST) + +Из `DOC/Ссылки для доступа в тестовые контуры.pdf`, `DOC/Инструкция по передаче эталонного запроса на перевод M2M 08.04 (1).pdf`, `DOC/Инструккия M2M.pdf`, `DOC/Презентация MOEX MOST.pdf`, `DOC/Презентация тестирование систем НРД.pdf`: + +- Контуры: **GUEST** (текущая прод-версия для тестов с клиентами), **TEST3** (перспективная, для ОЭ), **PROD** (по «Анкете НРД»). +- Эндпоинты ГОСТ (`gost-*.nsd.ru`) и RSA (`rsa-*.nsd.ru`) — раздельно. + +### Каналы взаимодействия + +1. **Интеграционный шлюз НРД (ИШ) с REST API — ОСНОВНОЙ КАНАЛ.** + - НРД-шный софт, ставится локально на наш сервер, имеет **REST API**: `POST /api/package/{channel}/file` (отправка пакета как ZIP в base64), `GET /api/package/status/{id}` (статус), `GET /api/package?channel=&date=&type=...` (входящие). + - **ИШ сам формирует пакет ЭДО, подписывает его и шифрует по Правилам ЭДО НРД.** Это резко уменьшает наш скоуп: для основного пути нам не нужен XMLDSig. + - Альтернативный режим ИШ — обменные папки (OUTBOX/INBOX/`#M2MTR`/`#M2MTD`/`#M2MER`), как fallback при недоступности REST. +2. **Web-сервис ONYX (WSL) — резервный/прямой канал.** + - URL: `https://gost-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo` (TEST3, ГОСТ), аналогично для GUEST/RSA. + - WSDL: `.../OnyxEdo/OnyxEdo.wsdl`. + - **Подпись и упаковка пакета — на нашей стороне** (через `crypto-service`). + - Используем для диагностики, при отказе ИШ, и в сценариях, где ИШ не подходит. +3. **Файловый шлюз НРД (ФШ).** + - Файловый обмен по тем же папкам, что и ИШ-обменные. Поддерживается «на всякий случай», в основном потоке не используем. + +В `admin-ui` — переключатель «канал отправки» для теста и эксплуатационных сценариев. Для каждого сценария фиксируется фактический канал в журнале. + +### Типы пакетов ЭДО (важно для маршрутизации в ИШ) + +| Тип пакета | Содержимое | Сценарий | +|------------|-----------|----------| +| `#M2MTR` | M2MTransferRequest | Запрос на перевод M2M | +| `#M2MTD` | M2MTransferDecision | Решение принимающей стороны | +| `#M2MER` | M2MTransferResponse (ошибки сервиса MOST) | Тех. ошибка / отказ форматно-логического контроля | +| `SUBBR` | `Transfer_out_request.xml` / `Transfer_in_request.xml` / `Transfer_in_consent.xml` | Старый сценарий «брокер→брокер» (поддержка для совместимости) | +| `SUBER` | `Transfer_reject.xml` | Отказ в старом сценарии и **автоматический отказ MOST по таймауту** | +| `SUB16` | Черновики **16-х/16-1 поручений** (по одной ЦБ в одном сообщении) | Подготавливаются НРД после согласования M2M, отправляются обоим брокерам | +| Справка о расходах | `Assets_investment_account_transfer_details.xml` + `Assets_investment_details_status.xml` (статус) | Передача справки о расходах, связанных с приобретением и хранением ЦБ, между брокерами через MOST | +| Стандартная квитанция ЭДО | служебное | Подтверждение доставки от НРД на каждый отправленный пакет | + +Конфиг — профили `guest-gost|guest-rsa|test3-gost|test3-rsa|prod-gost|prod-rsa`. Тёплый пул HTTP-соединений (`Transport.MaxIdleConnsPerHost`) для SLA. + +## Тиражируемость продукта (внутренний MVP → другие компании) + +Сейчас разрабатываем «для себя», но в архитектуру **с самого начала** закладываем возможность лёгкой передачи системы другим брокерам. Принципы: + +- **Никаких хардкодов заказчика в коде.** ИНН, коды депонента, имена брокера, цвета бренда, тексты писем, шаблоны уведомлений — только через конфиг или БД настроек, доступных в `admin-ui`. +- **Конфигурация одной командой.** При первом запуске в новой компании — мастер-«первоначальная настройка»: реквизиты юрлица, профиль ЭДО НРД (контур, тип ключей), путь к ИШ, провайдеры уведомлений, контракты ESIA Finance ЛК (URL, basic-auth-токен), путь к контейнерам ключей КриптоПро. +- **Лёгкое обновление версий у клиентов.** Артефакт поставки — собранные docker-образы + один скрипт `install.sh` / `update.sh` (поднимает docker-compose на ВМ, накатывает миграции БД, перезапускает только изменённые сервисы). Версии семантические (semver), changelog обязателен. Откат — на предыдущий тег образа одной командой. +- **СКЗИ-абстракция.** На M1 реализация — только КриптоПро. Но интерфейс `Provider` остаётся, чтобы другая компания со своим СКЗИ (Валидата CSP, VipNet) подключила свой провайдер, не трогая бизнес-логику. +- **Уведомления — провайдеры-плагины** (см. соответствующий раздел). Сейчас smtp + Yandex 360 + webhook; другая компания может включить Mattermost/Telegram/SMS-провайдер через UI. +- **Канал ЛК клиента — конфигурируемый** (см. ниже про ESIA Finance). Если у другой компании ЛК на иной платформе — реализуется отдельный адаптер и подменяется через конфиг. +- **Документация поставки**: руководство администратора (установка, настройка, обновление), руководство оператора, формуляр СКЗИ. На русском. +- **Лицензионная модель**: код — собственность правообладателя (наша компания), поставка — лицензия + поддержка. В реестр ПО заявка от нашего юрлица. + +Разделение «продуктовой части» (ядро, переиспользуется) и «настроек заказчика» (БД конфигов, шаблоны, реквизиты) — обязательно с самого первого спринта, иначе позже разбирать дороже. + +## Контракт с командой Fansy — что мы передаём + +Команда Fansy реализует ETL **на нашу БД**. Чтобы они могли начать, мы передаём им следующий пакет (готовится в M1, согласуется в начале M2): + +1. **DDL-скрипты PostgreSQL** для всех таблиц `fansy-store`. Минимально нужны (по требованиям документации НРД к данным M2M): + - **Депоненты / клиенты** (ИНН, ФИО/наименование, тип, привязка к учётной записи в ESIA Finance). + - **Документы инвестора, удостоверяющие личность** (тип `IdentityDocumentCodeEnum` 01–14/21–27/91, серия, номер). + - **ИИС-договоры** (`IIAContractTypeEnum ∈ {T12, T03}`, номер, дата, ИНН брокера) — для ветки ИИС. + - **Депо-счета и разделы** (`AccountId`, `SectionId`, `DeponentCode`, привязка к клиенту, статус, признак торгового раздела). + - **Реквизиты расчётов** (ИНН депозитария, код депонента, банковские реквизиты при необходимости). + - **Портфели и остатки ЦБ** (счёт + раздел + ISIN/`SecurityCode` + `IsolationStatus = SGDN` + кол-во `Whole/Fractional`) — для проверки достаточности на момент инициации перевода. + - **Справочник ЦБ** (`SecurityCode`, `ISIN`, `SecurityClassification ∈ {BOND, SHAR, MFUN}`, `SecurityCategory`, для ПИФ — `RegNumber`/`Class`). + - **Контрагенты-участники сервиса MOST** (`Справочник пользователей`: БКС/Ренессанс/Альфа-банк, ИНН, ОГРН, статус «доступен для M2M»). + - **Audit/staging-таблицы** для каждой основной (`*_staging`, `*_history`) — чтобы команда Fansy писала в staging, а наш сервис читал готовое. +2. **Контракт данных** на каждую таблицу: семантика поля, источник в Fansy (если известен), nullable, единицы, примеры. +3. **Тип load**: предпочтительно **инкрементный UPSERT по бизнес-ключу** в staging-таблицу + хвост-триггер в основную; вариант полной перезаливки оговаривается отдельно (только для справочников). +4. **Окна обновления и SLA на свежесть данных**: например, портфели/остатки — не позднее 1 минуты после изменения в Fansy; справочники ЦБ и контрагентов — раз в сутки или по событию. +5. **Технические требования**: способ подключения (отдельная роль PostgreSQL `fansy_etl` с правом INSERT/UPDATE на staging-схему, без DDL-прав), формат timestamp (UTC + явная зона), способ передачи ошибок (отдельная таблица `etl_errors`). +6. **Пример заявки M2M «end-to-end»**: какие поля из Fansy потребуются `m2m-core` для одной заявки и какой запрос будет выполнен — чтобы команда Fansy убедилась, что все нужные данные у них есть. +7. **Тестовые данные**: 5–10 тестовых клиентов, портфелей и заявок — для совместного приёмочного теста на стенде. +8. **График интеграции**: даты передачи DDL, даты первой выгрузки в `fansy-store`, дата начала совместного тестирования (в M4). + +Документ оформляется как `docs/fansy-contract/v1/...` в моно-репо: `ddl/`, `data-dictionary.md`, `etl-requirements.md`, `examples/`. Версионируется отдельно (`v1`, `v2`). + +## Российское ПО — встроенные требования (Реестр ПП РФ № 1236) + +### Что требуется по ПП-1236 для включения в Реестр + +Постановление Правительства РФ от 16.11.2015 № 1236 (с поправками) и Приказ Минцифры № 486 определяют условия включения ПО в Единый реестр российских программ. Ключевые критерии и наша готовность: + +| № | Требование ПП-1236 | Реализация в проекте | Статус | +|---|--------------------|----------------------|--------| +| 1 | **Правообладатель — российское лицо**: РФ, субъект РФ, муниципалитет, российская НКО (без преобладающего иностранного контроля) или гражданин РФ. Доля иностранного участия в УК ≤ 50% | Юрлицо заказчика — российское ООО/АО, проверяем долю иностранного капитала, готовим выписку из ЕГРЮЛ для заявки | **К проверке у заказчика** | +| 2 | **Исключительное право** на ПО принадлежит правообладателю на территории всего мира и на весь срок действия | Все договоры с разработчиками (штат + подряд) содержат пункты об отчуждении исключительного права; для open-source-зависимостей — лицензии совместимы (MIT/Apache 2.0/BSD/MPL/LGPL); GPL-копилефт исключаем | Закладываем в шаблоны договоров с M1 | +| 3 | **Свободная гражданская реализация** на территории РФ | Лицензионные договоры, ценник в рублях, оплата на расчётный счёт в РФ | Стандартная коммерческая модель — ОК | +| 4 | **Сумма выплат за рубеж < 30%** годовой выручки от реализации (роялти, лицензии иностранцам и т.п.) | Не используем платные иностранные SaaS/библиотеки; все облачные сервисы — российские | ОК (см. стек ниже) | +| 5 | **Сведения о ПО не составляют гостайну** | Не работаем с гостайной | ОК | +| 6 | **Класс ПО** по классификатору Минцифры | Заявляем как **«02.13 Системы электронного документооборота»** + **«02.09 Прикладное ПО для финансового сектора»** (уточнить с патентным поверенным при подаче) | На M5 | +| 7 | **Размещение исходного кода и сборок на территории РФ** | Git-репозиторий в РФ-хостинге: **`https://git.zetit.ru/zuevav/Bridge-and-Join-s`** (заказчиковый Gitea/GitLab on-prem). CI на РФ-инфраструктуре (раннеры этого же сервера или собственная ВМ заказчика) | **ОК — репо уже есть** | +| 8 | **Техподдержка на территории РФ** | Договор поддержки с юрлицом-правообладателем, контактные данные на сайте, телефон РФ, поддержка на русском, SLA в часовом поясе MSK | На M5 | +| 9 | **Документация на русском** | Все артефакты (руководства администратора, оператора, формуляр СКЗИ, описание архитектуры, OpenAPI/AsyncAPI, тексты UI) — на русском с самого начала | Закладываем с M1 | +| 10 | **Совместимость с реестровой ОС или независимость от ОС** | Сборки и приёмочные тесты на **Astra Linux SE 1.7+** и **РЕД ОС 7.3** (минимум одна из реестровых ОС обязательна) | M2 — добавить в CI | + +### Стек — все компоненты соответствуют импортозамещению + +| Слой | Выбор | Реестр ПО / российское | +|------|-------|------------------------| +| ОС | **Astra Linux SE 1.7+** (целевая) или **РЕД ОС 7.3** (альтернатива) | Да, в Реестре | +| СУБД | **PostgreSQL Pro Certified** (Postgres Professional) | Да, в Реестре | +| JDK для `crypto-service` | **Liberica JDK** (BellSoft) или **Axiom JDK** (BellSoft / Axiom) | Да, в Реестре | +| СКЗИ | **КриптоПро CSP/JCP**, класс КС1 | Сертифицировано ФСБ, в Реестре | +| Среда исполнения Go | Open-source upstream — допустимо, ограничений нет | Open-source — ОК | +| Сборка / контейнеризация | Docker / Podman + docker-compose / systemd | Open-source — ОК; при k8s — Deckhouse в Реестре | +| Корпоративная почта/мессенджер интеграция | Внутренний SMTP заказчика + **Yandex 360 для бизнеса** | Yandex — российский сервис | +| Объектное хранилище | **MinIO** (open source, локально на ВМ) | Open-source — ОК | +| Шина (если выйдем за in-process) | **NATS JetStream** или **Apache Kafka** | Open-source — ОК | +| Хостинг репозитория и CI | **`git.zetit.ru`** (РФ-хостинг заказчика) | Российская инфраструктура | +| Фронтенд | React + Vite (open-source upstream); UI-kit — **Yandex Cloud UI Kit** или собственный | Open-source — ОК | +| Артефакт-репозиторий | **Nexus** on-prem или собственный реестр Docker | Self-hosted — ОК | + +### Что НЕ используем (и почему) + +- **GitHub / GitLab.com / Atlassian Bitbucket** как первичный репозиторий — для подачи в Реестр требуется размещение в РФ. На GitHub допустимо «зеркало» в режиме read-only. +- **JetBrains Space, Confluence Cloud, Slack, Notion** — иностранные SaaS, заменяем российскими аналогами (Yandex 360, Mattermost on-prem, YouTrack on-prem допустим как self-hosted). +- **Microsoft .NET, MS SQL Server, Oracle DB, Windows Server** — не в Реестре, импортозамещаем (см. стек). +- **AWS / GCP / Azure / Cloudflare** — облака недружественной юрисдикции; всё on-prem или РФ-облака (Yandex Cloud, VK Cloud, MTS Cloud). +- **GPL/AGPL-зависимости** в основном коде — несовместимы с проприетарной поставкой; в SBOM ведём контроль лицензий, GPL разрешаем только для отдельно стоящих утилит, не линкующихся к ядру. + +### Что уже учтено в архитектуре + +- **Локализация**: UI и документы на русском, MSK timezone, формат даты ISO 8601 с явной зоной как требует `NSDDateTimeType`. +- **Тиражируемость** (см. отдельный раздел): отсутствие хардкодов заказчика, конфигурируемый `Provider` СКЗИ, провайдеры уведомлений плагинами — это и для других компаний, и для аккуратной заявки в Реестр (один артефакт ПО, разные настройки, без копий продукта на каждого заказчика). +- **СКЗИ КС1** на сертифицированной ОС. +- **57580.1/.2** (защита информации в финансовых организациях): сегментация ВМ, журналирование, контроль доступа, шифрование «в покое» (PostgreSQL Pro TDE). +- **152-ФЗ + ПП-1119**: УЗ-2 (ПДн категории «иные»), DPIA, согласие инвестора, журнал доступа к ПДн, маскирование в логах. +- **Документация поставки** (руководство администратора, оператора, формуляр СКЗИ) — на русском, в скоупе M5. + +### Что нужно сделать дополнительно для подачи в Реестр + +Эти задачи добавлены в M5, но подготовка идёт с M1: + +1. **Юридический пакет** (готовится к моменту подачи): + - Выписка из ЕГРЮЛ правообладателя. + - Документы об отчуждении исключительного права (от каждого разработчика — трудовой договор + служебное задание / договор подряда). + - Договоры на технологии в составе (open-source — указать лицензии в SBOM; коммерческие — КриптоПро, Postgres Pro, Liberica, Astra/РЕД ОС — приложить копии). + - Анкета и формы Минцифры (заполняются через ЕСИА руководителя на портале reestr.digital.gov.ru). +2. **Технический пакет**: + - Описание процессов разработки и поддержки. + - SBOM (CycloneDX) — для контроля происхождения и лицензий. + - Инструкции по установке и эксплуатации на русском. + - Сборка-демонстратор для проверки экспертами Минцифры (типовая инсталляция на Astra/РЕД ОС, доступ предоставляется по запросу). +3. **Интеллектуальная собственность**: + - Регистрация ПО в Роспатенте (необязательно, но повышает доказательность исключительного права при споре). + - Свидетельство Роспатента — приложение к заявке в Минцифры. +4. **Соответствие классу ПО**: пройти классификатор и подать с правильным классом (02.13 / 02.09 / 02.04 «средства защиты информации» — уточнить с патентным поверенным). +5. **Сертификация ФСТЭК / ФСБ** (не для Реестра, но для применимости в финансовых организациях): + - Сертификат ФСТЭК на встроенные средства защиты (если потребуется по аттестации сегмента у заказчика) — обычно не нужен, если заказчик аттестует свой сегмент сам. + - Сертификат ФСБ на криптомодуль — у нас не нужен, мы используем сертифицированный КриптоПро (его сертификат прикладываем). + +### Открытые проверки у заказчика + +- Доля иностранного участия в УК правообладателя ≤ 50% (выписка из ЕГРЮЛ). +- Все разработчики — резиденты РФ или работают по договорам с правильными формулировками о передаче ИС. +- Расчёт суммы выплат за рубеж < 30% выручки от реализации (актуально, если есть лицензии иностранным правообладателям). +- Согласие правообладателя подавать заявку (решение собственников, протокол). + +### Вердикт + +С точки зрения архитектуры и стека — **всё закладывается корректно**, ни одно из выбранных технических решений не противоречит требованиям Реестра. Основные риски — **юридические/корпоративные** (правообладатель, ИС, выплаты за рубеж) и **процессные** (правильно выбранный класс ПО, грамотно оформленный пакет документов). Эти риски решаются на M5 при участии юриста и патентного поверенного, но проверки у заказчика стоит начать **уже на M1**, потому что несоответствие в составе УК или в договорах с разработчиками невозможно «дописать» позже. + +## Roadmap (полный MVP) + +**M1 — Каркас, контракты, стенды (4 нед)** +- Моно-репо, CI, линтеры, конвенции. +- Go-модели из XSD + (de)serializer windows-1251 + `NSDDateTimeType` + строгий валидатор по pattern (ReferenceId, ISIN, ИНН, DeponentCode). +- gRPC-контракт `crypto-service` с реализацией **КриптоПро JCP** (плюс сохранённая `Provider`-абстракция для тиражирования), установка КриптоПро CSP на ВМ. +- docker-compose: PostgreSQL Pro, MinIO, заглушки сервисов. +- **Установка и настройка Интеграционного шлюза НРД** на тестовой ВМ, проверка REST API (`POST /api/package/{channel}/file`). +- **Инициировать выпуск тестовых сертификатов GUEST/TEST3** (ГОСТ + RSA), снять противоречие СКЗИ (Валидата vs КриптоПро/VipNet) с НРД и заказчиком. +- Версионированный контракт `lk-gateway` ↔ `lk-emulator`, **совместимый с ESIA Finance API V1** (`/api/v1/back_office/claims`); тот же контракт предлагаем команде реального ЛК. + +**M2 — Сквозной сценарий «донор» через стенд (6 нед)** +- `lk-emulator` v1: веб-форма «загрузить заявление» (готовый XML или сборка из полей), эмуляция вызова `POST /api/v1/back_office/claims/`, кнопка «Подписать и отправить», просмотр статусов и callback-ответов от `lk-gateway`. +- `lk-gateway` принимает заявление от эмулятора, валидирует подпись. +- `fansy-store` v0: схема БД и миграции под принимаемые от Fansy данные; **передача командe Fansy перечня таблиц + DDL** как контракт; стартовое наполнение фикстурами. +- `m2m-core` FSM `Draft → Validated → SubmittedToNSD → AwaitingDecision → Confirmed/Rejected/TimedOut → Done`, с веткой `TimedOut` по регуляторному таймауту (10→5 мин). +- `crypto-service`: проверка подписи входящих квитанций ЭДО и ответов НРД, подпись действий оператора. +- `nsd-adapter` v1: **ИШ через REST API** как основной канал, прохождение эталонного запроса из `DOC/Инструкция по передаче эталонного запроса на перевод M2M 08.04 (1).pdf` на TEST3 с типом пакета `#M2MTR`, ожидание `#M2MTD`. +- Метрики SLA по этапам, дашборд в `admin-ui`. + +**M3 — Сценарий «реципиент», справочники, admin-ui v1 (6 нед)** +- Приём входящих от НРД через ИШ REST (`GET /api/package?channel=&type=M2MTD|M2MER|...`), формирование `M2MTransferDecision`. +- `M2MTransferHandbookRequest` / `Handbook` / `ParticipantForm` + кэш в локальной БД, плюс загрузка `Справочник пользователей` (БКС, Ренессанс Брокер, Альфа-банк). +- Поддержка дополнительных типов пакетов: **`SUB16` (черновики 16-х/16-1 поручений)** — приём, парсинг, отображение в журнале и передача депозитарию. +- Поддержка `SUBBR`/`SUBER` (старый сценарий перевода) для совместимости и `Assets_investment_account_transfer_details` + `Assets_investment_details_status` (Справка о расходах). +- Стандартная квитанция ЭДО — обязательная проверка для каждого исходящего пакета. +- `admin-ui`: журнал входящих/уходящих, фильтры (GUID/инвестор/статус/период/тип пакета), просмотр эталонов, переключатель канала (ИШ/ONYX/ФШ). +- `admin-ui`: раздел «Сертификаты» — обновление сертификатов, сроки, алерты. +- Роли: администратор, **администратор СКЗИ**, сотрудник депозитария, оператор, аудитор. + +**M4 — Ручное согласование, уведомления (4 нед)** +- Ветка manual-approval в FSM, UI кнопок согласования. +- `notify`: e-mail + Yandex Messenger (до-интеграция готового бота заказчика) + WS-push в `admin-ui`. +- **Архитектура провайдеров-плагинов** для notify (smtp, yandex360, webhook, telegram, mattermost) — реализуем минимум smtp + yandex360 + webhook, остальные — заглушками с примером. +- Раздел «Уведомления» в `admin-ui`: включение/выключение провайдеров, ввод токенов, тестовая отправка, маршрутизация. +- Конфигурируемые правила «отправить на ручное согласование». +- Совместное тестирование с командой Fansy-ETL: проверка наполнения `fansy-store` боевым потоком. + +**M5 — ИБ, нагрузочное, оптимизация на SLA 2 мин, прод (6 нед, до 01.09.2026)** +- Аудит ГОСТ Р 57580, моделирование угроз, документы для ФСТЭК. +- Нагрузочное (k6) с целевым TPS, верификация SLA 2 мин и регуляторного таймаута 5 мин. +- Подача в Реестр ПО (заявка готовится параллельно с M3-M4). +- Подключение **резервного канала через WS ONYX напрямую** для ситуации недоступности ИШ. +- Подключение промконтура НРД, ПСИ. +- **Жёсткий дедлайн**: вступление 124-ФЗ + Указания Банка России **01.09.2026**. + +## Критичные файлы и активы + +- `DOC/M2MSchemas_260408/*.xsd` — единственный источник правды для моделей. +- `DOC/Примеры/*.xml` — fixtures unit-тестов. +- `DOC/Эталонные сообщения/*.xml` — приёмочные fixtures. +- `DOC/Ссылки для доступа в тестовые контуры.pdf` — конфиги стендов. +- `DOC/Инструкция по передаче эталонного запроса на перевод M2M 08.04 (1).pdf` — сценарий приёмки M2. +- `DOC/instr_podkl_stend_v3.pdf` — подключение к стендам. +- `DOC/Справочник пользователей.pdf` — **перечень организаций-участников сервиса** (БКС ИНН 5406121446, Ренессанс Брокер 7709258228, Альфа-банк 7728168971 — последний на пилоте). Загружается в `fansy-store`/локальный справочник как white-list контрагентов и индикатор «доступен ли участник для M2M-перевода». Модель ролей операторов нашей системы — **отдельная**, проектируется в `admin-ui`. +- `DOC/so_cher_por.pdf` — схема обмена для **получения черновиков поручений** (типы пакетов `SUBBR`, `SUBER`, `SUB16` — черновики 16-х поручений на каждую ЦБ). +- `DOC/so_spravki.pdf` — схема обмена **Справки о расходах** (`Assets_investment_account_transfer_details.xml` + `Assets_investment_details_status.xml`) между Брокером 1 и Брокером 2 через MOST. +- `DOC/Презентация MOEX MOST.pdf` — целевая архитектура M2M, регуляторика (124-ФЗ, дедлайн 01.09.2026), таймауты 5/2 минуты, таймаут отсутствия ответа 10/5 минут. +- `DOC/Презентация тестирование систем НРД.pdf` — окружение тестирования: **СКЗИ «Валидата CSP»**, Криптосервис НРД (порты 48737/48200), требования к рабочему месту оператора для веб-кабинетов. +- `DOC/API ЛК ЕСИА.pdf` — это **НЕ ЕСИА Госуслуг**, а **API бэк-офиса платформы «ESIA Finance»** (`/api/v1/back_office/...`, Basic HTTP, JSON, UTF-8). Это и есть контракт ЛК клиента, через который работает наша интеграция; релевантные разделы: Заявки (`/claims`), Реквизиты (`/requisites`), Депо-счета (`/depo_accounts`), Сообщения (`/messages`), Сертификаты (`/control_certificates`), Пользователи (`/users`). + +## Верификация + +- **Unit**: round-trip сериализация всех 6 типов сообщений против эталонов (`xml-exc-c14n` сравнение). +- **Crypto**: подпись эталона `crypto-service` ГОСТ-ключом → проверка штатным верификатором СКЗИ (Валидата/КриптоПро) → green. +- **Контрактные тесты**: WireMock-стенд **ИШ REST API** (методы `/api/package/...`) и WS ONYX в CI; ночные прогоны на GUEST/TEST3. +- **Регуляторный таймаут**: интеграционный тест «отправили `#M2MTR` на стенде, не получили `#M2MTD` за 10 мин» → проверка, что MOST прислал `Transfer_reject.xml (SUBER)` и наша FSM перешла в `TimedOut`. +- **SLA**: синтетический тест каждого этапа в CI, fail при > 5 мин (старт) и > 2 мин (целевой). +- **E2E**: docker-compose, Playwright прокликивает сценарии **через `lk-emulator`** (загрузка заявления → подпись → отправка) и далее по `admin-ui` (приход → авто-обработка, приход → ручное согласование, приход → таймаут SLA → нотификация). Эмулятор — основной инструмент E2E на всём протяжении, пока реальный ЛК не подключён. +- **Fansy**: контрактный тест выгрузки против тестовой схемы Fansy. +- **Приёмка НРД**: эталонный сценарий 08.04 без ручных правок, GUID совпадает в Request/Response/Decision. + +## Открытые вопросы (нужно уточнить перед M1) + +- **API ЛК клиента**: подтверждено — концепция взаимодействия реального ЛК соответствует `API ЛК ЕСИА.pdf` (платформа **ESIA Finance**, `/api/v1/back_office/...`). Сами формы документа на стороне ЛК ещё не реализованы, поэтому в `lk-emulator` мы пишем «как-будто-ЛК», который вызывает наш `lk-gateway` точно по этому контракту; реальный ЛК подключится через тот же контракт без правок на нашей стороне. +- **СКЗИ — решено: КриптоПро.** В `crypto-service` поднимаем **КриптоПро JCP** (на JVM) и КриптоПро CSP на ВМ, согласуем версию и лицензию. Поддержку других провайдеров не убираем (`Provider`-абстракция остаётся), но в реализации — только КриптоПро. +- **Установка ИШ**: дистрибутив ИШ НРД получает заказчик (как участник ЭДО), наша сторона ставит ИШ на целевую ВМ и сопровождает. +- **Fansy-store**: ETL делает команда Fansy в автоматизированном режиме. Наша ответственность — спроектировать таблицы согласно требованиям документации НРД к данным M2M (поля `Header`, `Data`, реквизиты сторон, счета/разделы, остатки) и **передать команде Fansy перечень таблиц + DDL** как контракт. Согласуем тип load, частоту, окно простоя. +- **Подпись действий оператора**: решено — **серверная, от имени оператора** (через `crypto-service`) ради SLA 2 мин. Клиентская подпись отклонена. +- **Yandex Messenger**: используется **Yandex 360 для бизнеса**, на стороне заказчика **уже есть готовый бот** — мы его до-интегрируем (передаём токен/webhook/ID чатов через конфиг). +- **Класс СКЗИ — решено: КС1.** Программная защита без аппаратного ДСЧ/HSM. Используем сертифицированное исполнение КриптоПро CSP/JCP на сертифицированной ОС (РЕД ОС 7.3 (целевая)). На M5 — сборка формуляра и эксплуатационной документации СКЗИ под требования КС1. +- **Аутентификация в `admin-ui`**: внутренний SSO компании (LDAP/Keycloak) или собственная учётка? + +--- + +## Что добавлено по результатам повторного аудита документации + +Ниже — сводно, чтобы было видно, что план учёл все обнаруженные при ре-аудите факты: + +1. **Три транспортных канала к НРД** (ИШ REST — основной, WS ONYX — резерв, ФШ — fallback) и таблица типов пакетов ЭДО (`#M2MTR`/`#M2MTD`/`#M2MER`/`SUBBR`/`SUBER`/`SUB16`/Справки/квитанции). +2. **ИШ сам подписывает пакеты** — пересмотрена роль `crypto-service` (он теперь нужен в первую очередь для проверки входящих, подписи действий оператора и резерва через WS ONYX). +3. **СКЗИ Валидата CSP** добавлено как поддерживаемый провайдер; противоречие с лицензиями заказчика вынесено в открытые вопросы; в `crypto-service` — абстракция `Provider`. +4. **`API ЛК ЕСИА.pdf` идентифицирован как API ESIA Finance** (не госЕСИА). Контракт `lk-gateway`/`lk-emulator` приводится к этому API (`/claims`, `/requisites`, `/depo_accounts`, `/messages`, `/control_certificates`, `/users`). +5. **Регуляторика**: 124-ФЗ от 23.05.2025, Указание ЦБ, **дедлайн прода 01.09.2026**, таймаут MOST 10→5 мин — учтены в SLA и M5. +6. **`Справочник пользователей`** переинтерпретирован: это перечень контрагентов (БКС, Ренессанс Брокер, Альфа-банк), не модель ролей операторов. +7. **Дополнительные сценарии**: справка о расходах (`Assets_investment_account_transfer_details` + статус), черновики поручений `SUB16`, старый сценарий `SUBBR`/`SUBER` — добавлены в скоуп M3. +8. **XSD-уточнения**: исправлен `IIAContractTypeEnum` (`T12 | T03`, не `T01/T02/T03`); зафиксированы pattern для `ReferenceId`, `ISIN`, `DeponentCode`, ИНН, `NSDDateTimeType` (только зона МСК); полный список `IdentityDocumentCodeEnum`. +9. **Установка ИШ НРД** включена в M1 как обязательный шаг подключения к стенду. +10. **Регуляторный таймаут MOST** оформлен как ветка FSM `TimedOut` с интеграционным тестом на стенде. diff --git a/docs/tasks/PR-1-go-models-m2m.md b/docs/tasks/PR-1-go-models-m2m.md new file mode 100644 index 0000000..413e26a --- /dev/null +++ b/docs/tasks/PR-1-go-models-m2m.md @@ -0,0 +1,185 @@ +# PR-1: Go-модели M2M + парсер windows-1251 + round-trip тесты + +## Цель + +Реализовать типизированную доменную модель сообщений M2M по XSD НРД, +парсер/сериализатор XML в windows-1251 и round-trip тесты на эталонах. +Это первый осмысленный код в проекте, на нём основываются все +последующие модули (`nsd-adapter`, `m2m-core`, `lk-gateway`, +`lk-emulator`). + +## Источники правды + +- `DOC/M2MSchemas_260408/*.xsd` — все типы и структуры, namespace + `http://nsd.ru/schemas/m2m/...`, version `2026-04-08`. +- `DOC/Примеры/*.xml` — примеры всех типов сообщений (windows-1251). +- `DOC/Эталонные сообщения/*.xml` — эталоны для приёмочной проверки. + +## Состав PR + +### 1. `internal/m2m/types.go` + +Все simple-типы и enum'ы из XSD как Go-типы. + +**Enum'ы (со списком допустимых значений):** + +- `StatusCode` — `INFO | ERROR`. +- `IIAContractType` — **`T12 | T03`** (именно так — T12 = обмен ИИС-1/ИИС-2, + T03 = ИИС-3; не T01/T02/T03). +- `SecurityClassification` — `BOND | SHAR | MFUN`. +- `SecurityCategory` — `ORDN | PREF | UKWN`. +- `IdentityDocumentCode` — `01 | 02 | 03 | 04 | 05 | 06 | 07 | 09 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 26 | 27 | 91`. +- `IsolationStatus` — только `SGDN`. + +**Simple-типы как Go-обёртки над `string`:** + +- `DeponentCode`, `ReferenceId`, `ISIN`, `OrganizationINN`, `UUID`, + `AccountId`, `SecurityCode`, `IdentityDocSerial`. + +### 2. `internal/m2m/validators.go` + +Метод `Validate() error` на каждом типе из пункта 1, проверяющий pattern +и длину из XSD: + +| Тип | Pattern | Длина | +|-----|---------|-------| +| `ReferenceId` | `^M2M[A-Z0-9]{13}$` | ровно 16 | +| `ISIN` | `^[A-Z]{2}[A-Z0-9]{9}[0-9]$` | ровно 12 | +| `OrganizationINN` | `^[0-9]{10}$` | ровно 10 | +| `DeponentCode` | `^[A-Z0-9]+$` | до 12 | +| `UUID` | стандартный UUID v1-5 pattern | 36 | +| `SecurityCode` | `^[0-9A-Z_/-]+$` | ровно 12 | +| `IdentityDocSerial` | `^\S+$` | от 1 | + +Enum'ы валидируются проверкой принадлежности к допустимому множеству. + +### 3. `internal/m2m/messages.go` + +Структуры всех 6 типов сообщений M2M: + +- `M2MTransferRequest` — Header (UUID, NSDDateTime, SenderCode, + ReceiverCode, CostInfo, опц. IIAAgreementDetails) + Data (IsM2M=true, + InvestorInformation, TransferringDepository, ReceivingDepository, + TransferredSecurities) + опц. NSDInfo. +- `M2MTransferDecision` — Header (тот же набор минус IIAAgreementDetails) + + Data (ReceivingDepository, Security[] с TransferDecision Choice). +- `M2MTransferResponse` — GUID + StatusCode + Response[] + (ReferenceId, Code, Text). +- `M2MTransferHandbook` + `M2MTransferHandbookRequest`. +- `M2MTransferParticipantForm`. + +**Choice-типы.** Реализуй через указатели на взаимоисключающие поля: + +- `CostInfo { Yes *Yes; No *No }` — ровно один не nil. +- `Quantity { Whole *uint64; Fractional *Decimal16 }`. +- `SecurityDetails { ISIN *ISIN; SecurityInfo *SecurityDescription }`. +- `IdentificationDetails { RegNumber *string; FundShares *FundShares }`. +- `DecisionTransfer { Confirmation *Confirmation; Rejection *Rejection }`. + +В `Validate()` каждого choice — проверка «ровно одно поле задано». + +**Фиксированные значения.** `IsM2M` всегда `true` — захардкодь +в `MarshalXML` (не выноси в поле, заполняй автоматически). + +**XML-теги.** Для каждого поля укажи `xml:"..."` с правильным namespace +из XSD. Используй namespace-aliases: + +- `rt` для `http://nsd.ru/schemas/m2m/request`, +- `dn` для `http://nsd.ru/schemas/m2m/decision`, +- `hk` для `http://nsd.ru/schemas/m2m/handbook`, +- `hr` для `http://nsd.ru/schemas/m2m/handbook/request`, +- `pf` для `http://nsd.ru/schemas/m2m/participant/form`, +- `m2m` для `http://nsd.ru/schemas/m2m/types`. + +### 4. `internal/nsdxml/datetime.go` + +Тип `NSDDateTime` для нестандартного формата НРД. + +- Формат: `YYYY-MM-DDThh:mm:ss(МСК[+-N])`, пример: `2026-03-02T14:30:45(МСК+2)`. +- Regex из XSD: + `^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\(МСК([+-][0-9]{1,2})?\)$`. +- Внутреннее представление — `time.Time` в location `Europe/Moscow` + плюс смещение (если есть). +- Реализуй: `MarshalXML`, `UnmarshalXML`, `MarshalText`, `UnmarshalText`, + `String() string`, `Now() NSDDateTime`. +- Кейсы для тестов: `(МСК)`, `(МСК+2)`, `(МСК-1)`, `(МСК+12)`. + +### 5. `internal/nsdxml/codec.go` + +Marshal/Unmarshal XML в windows-1251. + +- Зависимость: `golang.org/x/text/encoding/charmap`. +- `Marshal(v any) ([]byte, error)`: + 1. Сериализуй в utf-8 через `encoding/xml`. + 2. Прогони через `charmap.Windows1251.NewEncoder()`. + 3. Замени первую строку на + ``. +- `Unmarshal(data []byte, v any) error`: + 1. Создай `xml.Decoder` с `CharsetReader`, в котором при + `charset == "windows-1251"` оборачиваем reader в + `charmap.Windows1251.NewDecoder().Reader(...)`. + 2. Декодируй в `v`. +- Проверь, что кириллица в эталонах (ФИО инвестора, наименования + организаций) читается и пишется корректно. + +### 6. `internal/m2m/messages_test.go` + +Round-trip тесты на всех эталонах. + +- Таблица «файл → ожидаемый тип сообщения» для всех XML в + `DOC/Примеры/` и `DOC/Эталонные сообщения/`. +- Для каждого файла: + 1. `Unmarshal` → структура `S1`. + 2. `S1.Validate()` — без ошибок. + 3. `Marshal(S1)` → байты `B2`. + 4. `Unmarshal(B2)` → структура `S2`. + 5. `reflect.DeepEqual(S1, S2)` — true. +- Юнит-тесты валидаторов на негативных кейсах (короткий ИНН, неверный + префикс ReferenceId, неизвестное значение enum). +- Юнит-тесты `NSDDateTime` на всех вариантах зоны. + +## Требования к коду + +- **Без эмодзи** в коде и комментариях. +- Комментарии в коде — на русском. +- Имена типов и полей — на английском, как в XSD (`SettlementAccount`, + `IsolationStatus`, `IIAAgreementDetails` и т. п.). +- `go mod tidy` после добавления зависимостей (понадобится + `golang.org/x/text/encoding/charmap`). +- `make ci` (`tidy + fmt + vet + lint + test + build`) — зелёный. +- Покрытие тестами `internal/m2m/` и `internal/nsdxml/` — не менее 70%. + +## Коммит + +Один commit, сообщение: + +``` +feat(m2m): доменная модель сообщений + парсер windows-1251 + round-trip тесты + +- internal/m2m/types.go: enum'ы и simple-типы из XSD НРД (M2MSchemas_260408) +- internal/m2m/validators.go: pattern-валидаторы ReferenceId/ISIN/INN/... +- internal/m2m/messages.go: структуры 6 типов сообщений M2M +- internal/nsdxml/datetime.go: тип NSDDateTime (формат "YYYY-MM-DDThh:mm:ss(МСК+N)") +- internal/nsdxml/codec.go: Marshal/Unmarshal XML в windows-1251 +- internal/m2m/messages_test.go: round-trip тесты на всех эталонах из DOC/ + +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +Push в main (история линейная, ревью через diff в Gitea постфактум). + +## Проверка перед коммитом + +```bash +make ci +``` + +Если зелёное — push. Если красное — фиксируй ошибки до зелёного, +не коммить промежуточные правки. + +## После коммита + +1. Обнови `docs/tasks/README.md`: PR-1 — «выполнено», sha коммита. +2. Обнови `internal/m2m/README.md` и `internal/nsdxml/README.md`, + убери пометку «реализация — задача M1» и добавь короткое описание + что есть. diff --git a/docs/tasks/PR-2-fansy-ddl.md b/docs/tasks/PR-2-fansy-ddl.md new file mode 100644 index 0000000..92dfa58 --- /dev/null +++ b/docs/tasks/PR-2-fansy-ddl.md @@ -0,0 +1,148 @@ +# PR-2: DDL принимающей БД для команды Fansy + +## Цель + +Спроектировать DDL принимающей БД `fansy-store` под требования +документации НРД к данным M2M и передать его команде Fansy как +контракт для их ETL. + +ETL **делает команда Fansy** в автоматизированном режиме. Наша +ответственность — схема таблиц, индексы, миграции, типизированный +Go-репозиторий чтения и контракт данных. + +## Источники правды + +- `DOC/M2MSchemas_260408/*.xsd` — какие поля и типы нужны для + формирования M2M-сообщений. +- `DOC/Справочник пользователей.pdf` — перечень контрагентов + (БКС 5406121446, Ренессанс Брокер 7709258228, Альфа-банк 7728168971). +- `docs/architecture/plan.md` — контракт с командой Fansy (раздел + «Контракт с командой Fansy — что мы передаём»). + +## Состав PR + +### 1. `docs/fansy-contract/v1/ddl/` — SQL-миграции + +PostgreSQL 16 / PostgreSQL Pro Certified. Используй `goose` или +просто пронумерованные файлы `001__init.sql`, `002__staging.sql` и т. д. + +Схема разделена на две: +- `fansy_staging` — куда пишет команда Fansy (только их роль может + делать INSERT/UPDATE); +- `fansy` — рабочая, куда переливаются актуальные данные триггерами + или процедурами после валидации. + +Минимальный набор таблиц (повторяй структуру и в staging, и в +рабочей): + +- `clients` — депоненты/инвесторы: + - `id` (uuid), `inn` (для юрлиц), `last_name`, `first_name`, + `middle_name`, `birth_date`, `created_at`, `updated_at`. +- `client_documents` — документы инвестора: + - `id`, `client_id (FK)`, `document_type` (enum по + `IdentityDocumentCode`), `series`, `number`, `issued_at`, + `issuer`. +- `iia_contracts` — ИИС-договоры: + - `id`, `client_id (FK)`, `agreement_type` (`T12` | `T03`), + `agreement_number`, `agreement_date`, `broker_inn`. +- `depo_accounts` — депо-счета и разделы: + - `id`, `client_id (FK)`, `deponent_code`, `account_id`, + `section_id`, `depository_inn`, `is_active`, `is_trading`. +- `settlement_requisites` — реквизиты расчётов: + - `id`, `inn`, `display_name`, `created_at`. +- `portfolios` — портфели/остатки ЦБ: + - `id`, `client_id (FK)`, `depo_account_id (FK)`, `security_code`, + `isin`, `quantity_whole` (numeric), `quantity_fractional` + (numeric(38,16)), `isolation_status` (`SGDN`), `valued_at`. +- `securities` — справочник ЦБ: + - `id`, `security_code` (PK), `isin`, `classification` + (`BOND`|`SHAR`|`MFUN`), `category` (`ORDN`|`PREF`|`UKWN`), + `security_type`, `security_series`, `reg_number`, + `fund_class`, `display_name`. +- `participants` — контрагенты-участники сервиса MOST: + - `id`, `inn` (PK), `ogrn`, `full_name_rus`, `short_name_rus`, + `display_name_rus`, `full_name_eng`, `short_name_eng`, + `display_name_eng`, `depository_participant_code`, + `broker_participant_code`, `is_available_for_m2m`, `comment`. + - Предзаполни записями из `Справочник пользователей.pdf`. + +Дополнительно: +- `etl_errors` — таблица для ошибок выгрузки Fansy (id, source_table, + source_pk, payload jsonb, error_message, created_at). +- В `_staging`-таблицах поле `loaded_at timestamptz default now()`. + +**Индексы.** Минимум: PK, FK, индекс по `inn` и `deponent_code` в +основных таблицах, индекс по `valued_at` в `portfolios`. + +**Роль `fansy_etl`.** В отдельном `000__roles.sql`: +- роль `fansy_etl` с правами INSERT/UPDATE/SELECT на схему + `fansy_staging`; +- роль `bj_reader` с SELECT на схему `fansy` — для нашего + `m2m-core`/`lk-gateway`; +- DDL-права (CREATE/ALTER/DROP) — только у миграционной роли, + не у Fansy-ETL. + +### 2. `docs/fansy-contract/v1/data-dictionary.md` + +Таблица: «поле → семантика → источник в Fansy (если известен) → +nullable → пример». На каждую колонку каждой таблицы. + +### 3. `docs/fansy-contract/v1/etl-requirements.md` + +Технические требования к процессу выгрузки от команды Fansy: + +- Подключение: PostgreSQL, роль `fansy_etl`, отдельная учётная запись. +- Тип load: **инкрементный UPSERT в staging-таблицы** по бизнес-ключу; + полная перезаливка — только для справочников (`securities`, + `participants`), не чаще раза в сутки. +- SLA свежести данных: + - `portfolios` — не позднее 1 минуты после изменения в Fansy; + - `clients`, `depo_accounts`, `client_documents`, `iia_contracts` + — не позднее 5 минут; + - `securities`, `participants` — раз в сутки или по событию. +- Формат timestamp — UTC с явной зоной (`timestamptz`). +- Обработка ошибок: запись в `etl_errors`. +- Окно простоя для регламентных работ — согласовать. + +### 4. `docs/fansy-contract/v1/examples/` + +- `example-claim.md` — какие поля из БД нужны `m2m-core` для одной + типовой M2M-заявки (с конкретными SQL-запросами). +- `seed-data.sql` — 5–10 тестовых клиентов, портфелей, заявок для + совместного приёмочного теста. + +### 5. `migrations/fansy-store/` + +Скопировать `*.sql` из `docs/fansy-contract/v1/ddl/` — отсюда будут +исполняться миграциями в нашем сервисе. + +### 6. Обновить `docs/fansy-contract/v1/README.md` + +Описать состав каталога и порядок согласования с командой Fansy. + +## Требования к коду + +- Все DDL-файлы — на одной кодировке UTF-8. +- Имена таблиц и колонок — `snake_case` английский. +- Комментарии в DDL (`COMMENT ON COLUMN ... IS '...'`) — на русском. +- Без эмодзи. + +## Коммит + +``` +feat(fansy-store): DDL принимающей БД + контракт данных для команды Fansy + +- docs/fansy-contract/v1/ddl/ — миграции PostgreSQL +- docs/fansy-contract/v1/data-dictionary.md — словарь данных +- docs/fansy-contract/v1/etl-requirements.md — требования к ETL +- docs/fansy-contract/v1/examples/ — пример заявки + seed-data +- migrations/fansy-store/ — рабочие копии миграций +- предзаполнен справочник контрагентов из DOC/Справочник пользователей.pdf + +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +## После коммита + +1. Обновить `docs/tasks/README.md`: PR-2 — «выполнено». +2. Сообщить заказчику, что DDL готов и можно отправлять команде Fansy. diff --git a/docs/tasks/PR-3-lk-openapi.md b/docs/tasks/PR-3-lk-openapi.md new file mode 100644 index 0000000..6123c10 --- /dev/null +++ b/docs/tasks/PR-3-lk-openapi.md @@ -0,0 +1,113 @@ +# PR-3: OpenAPI контракт `lk-gateway` (ESIA Finance API V1) + +## Цель + +Зафиксировать REST-контракт между `lk-gateway` (наш сервис) и ЛК +клиента на платформе ESIA Finance. Контракт — отправная точка для: + +- собственного эмулятора ЛК (`lk-emulator`, PR будет позже); +- интеграции с командой реального ЛК (когда формы документа на + стороне ЛК будут готовы). + +## Источники правды + +- `DOC/API ЛК ЕСИА.pdf` — официальное описание API V1 платформы + ESIA Finance (`/api/v1/back_office/...`, Basic HTTP, JSON, UTF-8). +- `DOC/M2MSchemas_260408/*.xsd` — поля заявления должны быть + достаточны для формирования `M2MTransferRequest`. + +## Состав PR + +### 1. `docs/lk-contract/v1/openapi.yaml` + +Спецификация OpenAPI 3.0. Покрывает следующие операции: + +- `POST /api/v1/back_office/claims/` — создание заявки на M2M-перевод. + - Тело: подписанное заявление инвестора (XML или JSON, согласовать + с командой ЛК; XML с XMLDSig — предпочтительнее, проще + верифицировать на нашей стороне через `crypto-service`). + - Ответ: `{ id, status, created_at, success: true }`. +- `GET /api/v1/back_office/claims/{id}` — получение заявки и её + статуса. +- `PATCH /api/v1/back_office/claims/{id}` — обновление статуса от + нашей стороны (callback). +- `GET /api/v1/back_office/claims` — список заявок (фильтры по статусу, + периоду, инвестору). +- Аутентификация: Basic HTTP (как в API V1 ESIA Finance). +- Формат ошибок — как в `API ЛК ЕСИА.pdf`: + `{ error: true, status, code, title, meta: { message, errors } }`. + +### 2. Состав модели «Заявка» + +Минимальный набор полей, нужный для формирования +`M2MTransferRequest`: + +- `id` (uuid), +- `created_at`, `updated_at`, +- `status` — enum (`draft|signed|submitted|in_progress|confirmed|rejected|timed_out`), +- `investor` — ссылка на пользователя в ЛК (или встроенные ФИО+документ); +- `transferring_depository_inn`, `receiving_depository_inn`, +- `securities[]` — список с `security_code`, `isin` (или + `security_info`), `quantity_whole|fractional`, `settlement_accounts[]`, +- `cost_info` — `yes { code } | no`, +- `iia_agreement` (опц., для ИИС) — `agreement_type T12|T03`, + `agreement_number`, `agreement_date`, `broker_inn`, +- `signed_document` (base64) — подписанный XML заявления, +- `signature_format` — `XMLDSig-GOST` или `XMLDSig-RSA`. + +### 3. Состав модели «Callback статуса» + +Что мы отправляем обратно в ЛК: + +- `claim_id`, +- `new_status` (по тому же enum), +- `reason_code` (для отказов — код причины из M2MTransferResponse), +- `reason_text`, +- `updated_at`, +- `nsd_response` — оригинал ответа НРД (опц., для аудита). + +### 4. `docs/lk-contract/v1/examples/` + +- `examples/claim-request.json` — пример заявки на перевод + (3 ЦБ, ИИС, заполненные реквизиты). +- `examples/claim-response.json` — пример ответа. +- `examples/callback-confirmed.json` — пример callback подтверждения. +- `examples/callback-rejected.json` — пример callback отказа. +- `examples/error-422.json` — пример ошибки валидации. + +### 5. `docs/lk-contract/v1/changelog.md` + +Версионирование контракта. Первая запись — `v1.0.0`. + +### 6. Обновить `docs/lk-contract/v1/README.md` + +Описать состав, способ работы и порядок согласования с командой ЛК. + +## Требования к коду + +- OpenAPI 3.0.x, валидный по `spectral` или `openapi-cli`. +- Имена операций (operationId) — `snake_case`. +- Описания (description) — на русском. +- Все поля с `description` и примерами. +- Без эмодзи. + +## Коммит + +``` +feat(lk-contract): OpenAPI контракт lk-gateway по ESIA Finance API V1 + +- docs/lk-contract/v1/openapi.yaml — спецификация +- docs/lk-contract/v1/examples/ — примеры запросов/ответов/callback +- docs/lk-contract/v1/changelog.md — v1.0.0 + +Контракт предлагается команде реального ЛК как точка синхронизации. +В lk-emulator (отдельный PR) контракт реализуется как «как-будто-ЛК» +для проверки сквозного потока. + +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +## После коммита + +1. Обновить `docs/tasks/README.md`: PR-3 — «выполнено». +2. Сообщить заказчику, что OpenAPI готов и можно отправлять команде ЛК. diff --git a/docs/tasks/PR-4-m2m-core-skeleton.md b/docs/tasks/PR-4-m2m-core-skeleton.md new file mode 100644 index 0000000..2463114 --- /dev/null +++ b/docs/tasks/PR-4-m2m-core-skeleton.md @@ -0,0 +1,186 @@ +# PR-4: Каркас `m2m-core` — FSM сделки, репозитории, идемпотентность + +## Цель + +Реализовать ядро бизнес-логики M2M-перевода: конечный автомат сделки, +типизированный доступ к БД, идемпотентность по GUID, метрики SLA по +этапам. Сетевые интеграции (НРД, ЛК, crypto-service) пока +**заглушками через интерфейсы** — реальные клиенты подключаются +в PR-5 и далее. + +## Зависимости + +- PR-1 (Go-модели M2M) — для типизированных сообщений. +- PR-2 (DDL Fansy) — на схему БД. + +## Источники + +- `docs/architecture/plan.md` — раздел «Поток одной заявки end-to-end» + и «SLA и регуляторные сроки». +- `internal/m2m/` — модели сообщений (после PR-1). + +## Состав PR + +### 1. Конечный автомат сделки + +`internal/m2mcore/fsm.go`: + +``` +Draft → Validated → SubmittedToNSD → AwaitingDecision → + Confirmed → AwaitingSUB16 → Done + ↘ Rejected + ↘ TimedOut (после 10→5 мин без Decision) + ↘ ManualApproval (опц. ветка) +``` + +Переходы — явные методы: +- `(d *Deal) Validate() error`, +- `(d *Deal) Submit(ctx) error`, +- `(d *Deal) ReceiveDecision(ctx, m2m.M2MTransferDecision) error`, +- `(d *Deal) Timeout(ctx) error`, +- `(d *Deal) SendToManualApproval(ctx, reason string) error`, +- `(d *Deal) ApproveManually(ctx, operator string) error`, +- `(d *Deal) RejectManually(ctx, operator, code, comment string) error`. + +Каждый переход: +- проверяет текущее состояние (нельзя `Submit` если не `Validated`); +- пишет событие в `deal_events` (event sourcing для аудита); +- эмитит метрику `m2m_stage_duration_seconds{stage=...}`. + +### 2. Доменная модель сделки + +`internal/m2mcore/deal.go`: + +```go +type Deal struct { + ID uuid.UUID + GUID m2m.UUID // тот, что в M2MTransferRequest.Header.GUID + State State + InvestorID uuid.UUID + SignedClaim []byte // подписанный XML от ЛК + Request *m2m.M2MTransferRequest + Response *m2m.M2MTransferResponse + Decision *m2m.M2MTransferDecision + CreatedAt time.Time + UpdatedAt time.Time + Stages []StageRecord // история состояний с timestamp +} +``` + +### 3. Репозиторий + +`internal/m2mcore/repo.go`: + +Интерфейс `Repository` с реализацией на pgx: + +```go +type Repository interface { + Create(ctx, *Deal) error + GetByGUID(ctx, m2m.UUID) (*Deal, error) + Update(ctx, *Deal) error + List(ctx, Filter) ([]*Deal, error) + AppendEvent(ctx, *Deal, Event) error +} +``` + +Идемпотентность: `Create` использует `INSERT ... ON CONFLICT (guid) DO NOTHING RETURNING ...` — +повторный запрос с тем же GUID не создаёт дубль, а возвращает существующий. + +### 4. Внешние зависимости — интерфейсы (для PR-5+) + +`internal/m2mcore/ports.go`: + +```go +type NSDSender interface { + Send(ctx, *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error) + SendDecision(ctx, *m2m.M2MTransferDecision) error +} + +type LKCallbackClient interface { + UpdateStatus(ctx, claimID, status, reason string) error +} + +type CryptoVerifier interface { + VerifyXMLDSig(ctx, payload []byte) (CertInfo, error) +} + +type FansyStore interface { + GetClientByID(ctx, uuid.UUID) (*Client, error) + GetDepoAccount(ctx, deponentCode, accountID string) (*DepoAccount, error) + CheckBalance(ctx, depoAccountID uuid.UUID, isin string) (Quantity, error) +} +``` + +В PR-4 — заглушки `noopNSDSender`, `noopLKCallbackClient` и т. п. +Реальные клиенты подключаются в PR-5, PR-6. + +### 5. Сборка enrichment Request из заявки + +`internal/m2mcore/enrich.go`: + +Функция «из заявки от ЛК + данные из `fansy-store` → `M2MTransferRequest`». + +Шаги: +1. Распарсить подписанное заявление (XML от ЛК). +2. Поднять реквизиты сторон, депо-счета, остатки из FansyStore. +3. Проверить достаточность остатков по каждой ЦБ. +4. Сгенерировать `GUID` (uuid v4) и `ReferenceId` для каждой ЦБ + (`M2M` + дата `YYYYMMDD` + 5 случайных символов A-Z0-9). +5. Заполнить `Header.CreationTimestamp` = `nsdxml.NSDDateTime.Now()`. +6. Вернуть готовый `*m2m.M2MTransferRequest`, валидированный. + +### 6. Метрики SLA + +`internal/m2mcore/metrics.go`: + +Prometheus метрики: +- `m2m_stage_duration_seconds{stage="..."}` — histogram длительности этапа, +- `m2m_deals_total{state="..."}` — counter сделок по итоговому состоянию, +- `m2m_sla_breaches_total{stage="...",budget="5m|2m"}` — counter + превышений 80% бюджета SLA. + +Endpoint `/metrics` поднимается в `cmd/m2m-core/main.go`. + +### 7. Заменить заглушку `cmd/m2m-core/main.go` + +Минимальный рабочий сервер: +- читает конфиг (env: `BJ_DSN`, `BJ_LOG_LEVEL`, ...), +- открывает соединение с Postgres, +- инициализирует Repository, +- поднимает HTTP-эндпоинты `/healthz`, `/metrics`, +- логирует все запуски. + +### 8. Тесты + +- Юнит-тесты FSM (все переходы, в т. ч. недопустимые). +- Интеграционный тест с PostgreSQL через `testcontainers-go` (или + `dockertest`): создал сделку, обновил, прочитал, проверил + идемпотентность по GUID. +- Тест `enrich.go` на эталонной заявке (fixtures из примеров ESIA + Finance API). + +## Требования + +- Без эмодзи. Комментарии — на русском. +- `make ci` зелёный. +- Покрытие тестами `internal/m2mcore/` — не менее 60%. + +## Коммит + +``` +feat(m2m-core): FSM сделки, репозиторий на pgx, идемпотентность по GUID, метрики SLA + +- internal/m2mcore/fsm.go: конечный автомат с переходами и аудит-событиями +- internal/m2mcore/deal.go: доменная модель сделки +- internal/m2mcore/repo.go: репозиторий на pgx с идемпотентным Create +- internal/m2mcore/ports.go: интерфейсы для внешних зависимостей (заглушки) +- internal/m2mcore/enrich.go: сборка M2MTransferRequest из заявки + Fansy +- internal/m2mcore/metrics.go: Prometheus-метрики этапов и SLA +- cmd/m2m-core/main.go: минимальный сервер с /healthz и /metrics + +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +## После коммита + +Обновить `docs/tasks/README.md`: PR-4 — «выполнено». diff --git a/docs/tasks/PR-5-nsd-adapter-skeleton.md b/docs/tasks/PR-5-nsd-adapter-skeleton.md new file mode 100644 index 0000000..0e77497 --- /dev/null +++ b/docs/tasks/PR-5-nsd-adapter-skeleton.md @@ -0,0 +1,132 @@ +# PR-5: Каркас `nsd-adapter` — клиент к ИШ НРД (REST), резерв через WS ONYX + +## Цель + +Реализовать транспорт к НРД. Основной канал — Интеграционный шлюз НРД +через REST API (ИШ сам подписывает и упаковывает пакеты ЭДО, нам +XMLDSig не нужен). Резерв — WS ONYX (с подписью на нашей стороне через +`crypto-service`, реализуется когда КриптоПро JCP будет доступен). + +## Зависимости + +- PR-1 (Go-модели). +- PR-4 (`m2m-core` — туда инжектим `NSDSender`). +- Внешние: установленный ИШ НРД на ВМ, тестовые сертификаты GUEST/TEST3. + +## Состояние блокеров + +Этот PR можно начать **только после**: + +- НРД выдал тестовые сертификаты GUEST/TEST3 (ГОСТ или RSA); +- получен и установлен дистрибутив Интеграционного шлюза; +- доступен REST-эндпоинт ИШ (обычно `http://localhost:8080/api/...`). + +До этого — оставить PR-5 в очереди, не блокировать другие PR. + +## Источники + +- `DOC/Инструкция по передаче эталонного запроса на перевод M2M 08.04 (1).pdf` +- `DOC/Презентация MOEX MOST.pdf` +- `DOC/instr_podkl_stend_v3.pdf` +- `DOC/Ссылки для доступа в тестовые контуры.pdf` + +## Состав PR + +### 1. REST-клиент ИШ + +`internal/nsdadapter/igw/client.go`: + +Методы: +- `SendPackage(ctx, channel, packageType, body []byte) (packageID string, err error)` + → `POST /api/package/{channel}/file`, тело: ZIP в base64 в JSON. +- `GetStatus(ctx, packageID) (Status, err error)` + → `GET /api/package/status/{id}`. +- `ListIncoming(ctx, channel, since time.Time, packageType string) ([]Package, error)` + → `GET /api/package?channel=&date=&type=...`. + +Поддержка типов пакетов: `#M2MTR`, `#M2MTD`, `#M2MER`, `SUBBR`, `SUBER`, +`SUB16`, Assets_investment_*, стандартные квитанции ЭДО. + +### 2. Маршрутизация и пакетирование + +`internal/nsdadapter/router.go`: + +Функция «доменное сообщение → тип пакета ЭДО + содержимое ZIP». + +Каждое сообщение M2M упаковывается в ZIP вместе с `config.xml` по +Правилам ЭДО — но если ИШ работает в режиме «принимаю XML, сам +формирую пакет», достаточно отправить чистый XML и тип пакета. + +### 3. Реализация `NSDSender` из PR-4 + +`internal/nsdadapter/sender.go`: + +Реализует интерфейс `m2mcore.NSDSender`, использует REST-клиент ИШ. + +### 4. Конфигурация + +`internal/nsdadapter/config.go`: + +Профили: `guest-gost`, `guest-rsa`, `test3-gost`, `test3-rsa`, +`prod-gost`, `prod-rsa`. + +Каждый профиль: URL ИШ, путь к ключевому контейнеру (на стороне ИШ), +таймауты, retry-политика. + +### 5. Резервный канал WS ONYX (опц., если время позволяет) + +`internal/nsdadapter/onyx/`: + +SOAP-клиент к `OnyxEdoWSService`. Подпись пакета через +`crypto-service` (gRPC). Реализуется когда `crypto-service` будет +готов (PR-6). + +В PR-5 — структуры и заглушка с TODO. + +### 6. Опрос входящих + +`cmd/nsd-adapter/main.go`: + +Минимальный сервис: каждые N секунд (конфиг) делает +`ListIncoming` для актуальных типов пакетов, скачивает новые, +сохраняет в БД (`incoming_packages` таблица), эмитит событие в +`m2m-core` через шину или прямой вызов. + +### 7. Интеграционный тест на стенде + +`internal/nsdadapter/igw/integration_test.go`: + +- Помечен `//go:build integration`. +- Запускает эталонный запрос из + `DOC/Инструкция по передаче эталонного запроса на перевод M2M 08.04 (1).pdf` + на TEST3. +- Проверяет, что приходит `#M2MTD` (от тестового брокера 2) или + `#M2MER` (тех. ошибка). +- Поднимается только когда `BJ_INTEGRATION_TEST_NSD=1`. + +## Требования + +- Логировать каждое обращение к ИШ (метод, URL, HTTP-статус, + package_id, длительность). **Не логировать содержимое пакета** + (там могут быть ПДн). +- Маскировать ПДн (ФИО, документы) в любых логах. +- Без эмодзи. + +## Коммит + +``` +feat(nsd-adapter): REST-клиент ИШ НРД + маршрутизация типов пакетов + +- internal/nsdadapter/igw/: REST-клиент ИШ (SendPackage, GetStatus, ListIncoming) +- internal/nsdadapter/router.go: маршрутизация по типам пакетов ЭДО +- internal/nsdadapter/sender.go: реализация m2mcore.NSDSender +- internal/nsdadapter/config.go: профили GUEST/TEST3/PROD x ГОСТ/RSA +- internal/nsdadapter/onyx/: каркас резервного канала WS ONYX +- cmd/nsd-adapter/main.go: опрос входящих пакетов + +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +## После коммита + +Обновить `docs/tasks/README.md`: PR-5 — «выполнено». diff --git a/docs/tasks/PR-6-crypto-service-skeleton.md b/docs/tasks/PR-6-crypto-service-skeleton.md new file mode 100644 index 0000000..f40ed6f --- /dev/null +++ b/docs/tasks/PR-6-crypto-service-skeleton.md @@ -0,0 +1,166 @@ +# PR-6: Каркас `crypto-service` — Java + КриптоПро JCP, gRPC по UDS + +## Цель + +Реализовать сервис криптографических операций по ГОСТ: + +- проверка подписи входящих квитанций ЭДО, ответов НРД и сообщений + от брокеров; +- **серверная подпись действий оператора** в `admin-ui` (ради + соблюдения SLA 2 мин); +- резервный канал отправки в НРД через WS ONYX напрямую (когда ИШ + не задействован — упаковка и подпись на нашей стороне); +- криптографические проверки целостности эталонов в MinIO. + +## Зависимости + +- PR-1 (Go-модели). +- PR-4 (`m2m-core` — реализуем `CryptoVerifier`). +- Внешние: лицензия и дистрибутив КриптоПро CSP + JCP под РЕД ОС. + +## Состояние блокеров + +Этот PR можно начать **только после**: + +- получены лицензии КриптоПро CSP и JCP; +- установлен КриптоПро CSP на dev-ВМ; +- доступны тестовые ключевые контейнеры. + +До этого — заглушка с gRPC-протоколом, без реальной криптографии. + +## Источники + +- `docs/architecture/plan.md` — раздел «Криптография: Go + ГОСТ» и + «Подпись действий оператора». +- Документация КриптоПро JCP. +- Apache Santuario (XMLDSig). + +## Состав PR + +### 1. gRPC-протокол + +`services/crypto-service/proto/crypto.proto`: + +```proto +syntax = "proto3"; +package bridge_and_joins.crypto.v1; + +service CryptoService { + // Проверка подписи XMLDSig в пакете ЭДО. + rpc VerifyXMLDSig(VerifyRequest) returns (VerifyResponse); + + // Подпись XML по ГОСТ — для резервного канала WS ONYX и для + // действий оператора. + rpc SignXMLDSig(SignRequest) returns (SignResponse); + + // Health-check. + rpc Health(HealthRequest) returns (HealthResponse); +} + +message VerifyRequest { + bytes payload = 1; // XML с подписью + string profile = 2; // "guest-gost" | "test3-gost" | "prod-gost" | ... +} + +message VerifyResponse { + bool valid = 1; + string signer_cn = 2; // CN из сертификата + string signer_inn = 3; // ИНН из сертификата (если есть) + string serial = 4; + int64 not_before = 5; // unix epoch + int64 not_after = 6; + repeated string errors = 7; +} + +message SignRequest { + bytes payload = 1; // канонизированный XML + string key_alias = 2; // алиас ключа в JCP-keystore + string profile = 3; +} + +message SignResponse { + bytes signed_xml = 1; +} +``` + +### 2. Java-проект + +Структура: +``` +services/crypto-service/ +├── build.gradle.kts — Gradle с Liberica JDK 21 +├── src/main/java/.../CryptoServer.java — main, gRPC сервер +├── src/main/java/.../VerifyHandler.java — Santuario verify +├── src/main/java/.../SignHandler.java — Santuario sign +├── src/main/java/.../KeystoreProvider.java — Provider-абстракция +│ (КриптоПро / Валидата / ...) +└── src/test/java/.../*Test.java +``` + +Зависимости: +- gRPC Java + protoc-gen-java, +- Apache Santuario с ГОСТ-патчем, +- КриптоПро JCP (jar поставляется заказчиком вместе с лицензией). + +### 3. Бинд на UDS + +Сервер слушает Unix Domain Socket по пути из конфига (`/run/bj/crypto.sock`), +а не TCP — минимальная задержка, изоляция от сети. + +### 4. Provider-абстракция + +`KeystoreProvider` — интерфейс с реализациями `CryptoProJcpProvider`, +`ValidataJcpProvider` (заглушка), `VipNetProvider` (заглушка). Выбор — +через конфиг (env: `BJ_CRYPTO_PROVIDER=cryptopro`). + +### 5. Go-клиент + +`internal/cryptocli/client.go`: + +Клиент gRPC по UDS, реализует `m2mcore.CryptoVerifier`. Используется +из `lk-gateway` (верификация заявления), `m2m-core` (верификация +квитанций НРД), `nsd-adapter/onyx` (подпись для резервного канала). + +### 6. Тесты + +- Юнит-тесты Java на эталонах: подпиши тестовым ключом → провер' + → должен быть `valid=true`. +- Юнит-тесты Go-клиента на mock-сервере gRPC. +- Интеграционный тест (помечен `//go:build integration`): запускает + crypto-service как процесс и проверяет подпись/проверку + эталонной квитанции. + +### 7. Сборка и упаковка + +- `Dockerfile` для `crypto-service` (Liberica JDK 21 base). +- Подключение к `deploy/docker-compose/docker-compose.yml` (mount UDS + как volume). + +## Требования + +- **Закрытые ключи КриптоПро никогда не логируются и не выгружаются** + через API. Только handle на keystore. +- Каждая операция подписи — аудит-запись в журнале (`audit.log` или + отдельный gRPC stream): кто (оператор/системный ключ), что + подписано (sha256 от payload), когда. +- Без эмодзи. + +## Коммит + +``` +feat(crypto-service): gRPC-каркас сервиса криптографии (КриптоПро JCP) + +- services/crypto-service/proto/crypto.proto — protobuf-контракт +- services/crypto-service/ — Java 21 + gRPC, заглушки Verify/Sign +- internal/cryptocli/ — Go-клиент по UDS, реализует CryptoVerifier +- deploy/docker-compose/docker-compose.yml — подключение сервиса + +Реальная подпись КриптоПро подключается после получения лицензии и +ключевых контейнеров от заказчика. + +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +## После коммита + +Обновить `docs/tasks/README.md`: PR-6 — «выполнено». diff --git a/docs/tasks/README.md b/docs/tasks/README.md new file mode 100644 index 0000000..8ceaf10 --- /dev/null +++ b/docs/tasks/README.md @@ -0,0 +1,48 @@ +# docs/tasks — задачи разработки + +Здесь лежат **готовые промпты** для Claude Code, выполняемые в порядке +PR-1 → PR-N. Каждая задача — самостоятельный осмысленный PR в `main`. + +Архитектурный контекст и обоснование решений — в +`docs/architecture/plan.md` (полный план проекта) и +`docs/architecture/overview.md` (краткая выжимка). + +## Очередь задач + +| PR | Файл | Статус | Зависит от | +|----|------|--------|-----------| +| PR-1 | `PR-1-go-models-m2m.md` | готово к запуску | — | +| PR-2 | `PR-2-fansy-ddl.md` | готово к запуску | — (параллельно с PR-1) | +| PR-3 | `PR-3-lk-openapi.md` | готово к запуску | — (параллельно с PR-1) | +| PR-4 | `PR-4-m2m-core-skeleton.md` | готово к запуску | PR-1 | +| PR-5 | `PR-5-nsd-adapter-skeleton.md` | ждёт ИШ НРД и сертификаты | PR-1, PR-4 | +| PR-6 | `PR-6-crypto-service-skeleton.md` | ждёт КриптоПро JCP | PR-1 | + +## Как запустить задачу + +На dev-ВМ под `dev`, в корне репо: + +```bash +cd /srv/dev/Bridge-and-Join-s +git pull +claude +``` + +В сессии Claude Code: + +> Прочитай `docs/tasks/PR-1-go-models-m2m.md` и выполни задачу полностью. +> По завершении сделай commit и push в `main` (или открой MR, если так +> принято), обнови статус задачи в `docs/tasks/README.md` с «готово к +> запуску» на «выполнено» с указанием sha коммита. + +## Соглашения + +- **Без эмодзи** в коде и комментариях. +- Комментарии в коде — на русском, имена типов и полей — на английском + как в XSD/контрактах. +- Каждый PR проходит `make ci` зелёным. +- Перед коммитом — `go mod tidy`, `make fmt`, `make lint`. +- Сообщение коммита: `<тип>(<область>): <короткое описание>` + + расшифровка в теле. +- Документация изменений — в README соответствующего модуля + (`internal/<...>/README.md`).