docs: план проекта + промпты задач PR-1..PR-6 для Claude Code на ВМ
- 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>
This commit is contained in:
@@ -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) <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
## После коммита
|
||||
|
||||
Обновить `docs/tasks/README.md`: PR-4 — «выполнено».
|
||||
Reference in New Issue
Block a user