- docs/architecture/plan.md — полный план проекта (архитектура, стек, SLA, регуляторика, Реестр ПО, roadmap M1–M5, открытые вопросы и решения). - docs/tasks/README.md — индекс задач и инструкция запуска для Claude Code на dev-ВМ. - docs/tasks/PR-1-go-models-m2m.md — Go-модели M2M, парсер windows-1251, NSDDateTime, round-trip тесты на эталонах. - docs/tasks/PR-2-fansy-ddl.md — DDL принимающей БД для команды Fansy (контракт данных, ETL-требования, словарь полей, тестовые данные). - docs/tasks/PR-3-lk-openapi.md — OpenAPI контракт lk-gateway по ESIA Finance API V1, для синхронизации с командой ЛК. - docs/tasks/PR-4-m2m-core-skeleton.md — FSM сделки, репозиторий, идемпотентность по GUID, метрики SLA. - docs/tasks/PR-5-nsd-adapter-skeleton.md — REST-клиент ИШ НРД, маршрутизация типов пакетов (M2MTR/M2MTD/M2MER/SUBBR/SUBER/SUB16). - docs/tasks/PR-6-crypto-service-skeleton.md — gRPC-каркас Java-сервиса криптографии (КриптоПро JCP, UDS, Provider-абстракция). С этого коммита дальнейшая разработка идёт на dev-ВМ через запущенный там Claude Code. Промпты PR-1..PR-3 готовы к параллельному запуску; PR-4 после PR-1; PR-5 и PR-6 ждут поставку артефактов (ИШ НРД, сертификаты, КриптоПро JCP). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.3 KiB
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:
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:
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:
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».
Шаги:
- Распарсить подписанное заявление (XML от ЛК).
- Поднять реквизиты сторон, депо-счета, остатки из FansyStore.
- Проверить достаточность остатков по каждой ЦБ.
- Сгенерировать
GUID(uuid v4) иReferenceIdдля каждой ЦБ (M2M+ датаYYYYMMDD+ 5 случайных символов A-Z0-9). - Заполнить
Header.CreationTimestamp=nsdxml.NSDDateTime.Now(). - Вернуть готовый
*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) <noreply@anthropic.com>
После коммита
Обновить docs/tasks/README.md: PR-4 — «выполнено».