Event Contract: pin.profile.verification-request-submitted (VerificationRequestSubmittedKafkaEvent)
Версионируемый контракт интеграционного события «заявка на верификацию инвестора отправлена на ревью».
Consumer-документы ссылаются сюда, не переописывая payload. Дом полей —
entity: VerificationRequestEntity (раздел «Доменные и интеграционные
события»).
Назначение
Фиксирует переход заявки KYC/AML Draft → Submitted (метод Submit агрегата,
verification-request.md): инвестор заполнил ФИО, дату рождения,
паспортные данные, приложил сканы (инварианты INV-VRF-2 CompleteOnSubmit, INV-VRF-4 AdultOnly), и заявка с
зафиксированным иммутабельным зашифрованным снапшотом встала в очередь ревью сотрудников ПИН
(FIFO по SubmittedAt).
Событие — сигнальное, без PII: потребителям (notifications, support) достаточно знать, что появилась
работа для очереди ревью; сами данные заявки доступны только ревьюеру через admin API profile
(viewVerificationSnapshot, аудируемая расшифровка). Итог рассмотрения публикуется отдельным контрактом
того же топика — verification-completed (покрывает оба исхода:
Approved и Rejected).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.profile.verification-request-submitted |
| C# type | VerificationRequestSubmittedKafkaEvent |
| Topic | pin.profile.verification-requests.v1 (общий с VerificationCompletedKafkaEvent; общий key = порядок per-user) |
| Partitions / key | 3 партиции (ADR-0056: 1–10k активных) / key = OwnerUserId — строгий порядок submitted/completed per user |
| ContractVersion | static int ContractVersion = 1 |
| Producer | profile: handler submitVerification (public API, permission profile.verification.submit) |
| Owning team | PIN Platform Core |
| Статус | draft |
Метаданные события — идентификатор сообщения, время события
CreatedAt, версия (=ContractVersion), payload,AggregateId = Id(заявка),AggregateType = "VerificationRequest". Trace — на outbox-записи. Событие публикуется через транзакционный outbox атомарно с фиксацией снапшота заявки и обновлениемProfile.VerificationStatus → Pending(одно согласованное изменение: заявка +ProfileEntity+ событие).
TenantId отсутствует: инвестор вне tenant (ADR-0052); изоляция record-level по OwnerUserId.
Payload VerificationRequestSubmittedKafkaEvent
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | VerificationRequestEntity.Id | Immutable | Идентификатор заявки (= AggregateId). Один Id может быть отправлен повторно после Revoke → доработка → повторный Submit (см. идемпотентность потребителей). |
OwnerUserId | Guid | Да | VerificationRequestEntity.OwnerUserId | Immutable | Верифицируемая учётная запись (= Kafka key); получатель подтверждения «заявка принята». |
SubmittedAt | DateTimeOffset | Да | VerificationRequestEntity.SubmittedAt | Immutable per submit | Момент постановки в очередь ревью (FIFO-сортировка очереди). При повторном Submit после Revoke — новое значение. |
Вложенных моделей нет. Снапшот заявки (VerificationSnapshotModel), PersonId, сканы, статусы — в payload
не входят: PII не публикуется, а статусная детализация потребителям сигнала не нужна.
Когда публикуется
| Условие | Переход состояния | Транзакция |
|---|---|---|
submitVerification: метод Submit собрал и зашифровал снапшот (INV-VRF-2, INV-VRF-4, INV-VRF-7) | Draft → Submitted; параллельно `Profile.VerificationStatus: NotVerified | Rejected → Pending` |
Повторный Submit той же заявки после Revoke (доработка инвестором) | Draft → Submitted (новый снапшот, новый SubmittedAt) | та же |
Переход Submitted → Draft (Revoke) события не публикует: заявка просто исчезает из очереди ревью
(admin API читает очередь из БД profile по Status = Submitted), а потребители-сигналы отзыва не
обрабатывают (тикет support закрывается вручную/при итоговом событии).
Потребители (consumers)
| Consumer-контекст | Consumer | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|---|
notifications | NotificationsVerificationSubmittedConsumer | Подтверждение инвестору «заявка принята, рассматривается» (канал по настройкам получателя); служебное уведомление команде ревью о новой заявке в очереди | MessageId + business key Id:SubmittedAt (повторный Submit — новое уведомление, дубль доставки — skip) | < 5 мин |
support | SupportVerificationSubmittedConsumer | Тикет в очереди ревью KYC (маршрутизация на сотрудников с profile.verification.review); тикет содержит только Id/OwnerUserId — данные заявки открываются в admin API profile | тот же business key | < 5 мин |
Consumer-документы — будущие пути docs/06-services/notifications/consumers/profile-verification-submitted.md
и docs/06-services/support/consumers/profile-verification-submitted.md (контексты по будущим ТЗ;
конвенция ссылок).
Доставка и восстановление
- At-least-once через транзакционный outbox. Событие фиксируется в одном согласованном изменении с
переходом заявки
Draft → SubmittedиProfile.VerificationStatus → Pending; недоступность брокера не откатывает отправку заявки (снапшот зафиксирован в БД), публикация досылается ретраем. Возможны дубли. - Порядок per-user по key =
OwnerUserId—submittedиcompletedодного пользователя упорядочены. - Повторный Submit после Revoke порождает новое событие того же
Idс новымSubmittedAt; потребитель различает попытки по business keyId:SubmittedAt(новая попытка — новое уведомление/тикет; дубль доставки той же попытки — skip). - Revoke (
Submitted → Draft) события не публикует: заявка исчезает из очереди ревью (admin API читает очередь из БД поStatus = Submitted); тикет support закрывается вручную или по итоговомуverification-completed. - Poison-message. Необрабатываемое после ретраев событие → DLQ контекста-потребителя + alert; заявка в
profileостаётся источником истины (очередь читается из БД), восстановление — ре-плей из DLQ.
Версионирование и совместимость (реальность SDK)
ContractVersion = 1; приenvelope.Version != ContractVersionconsumer логирует warning и продолжает (upcasting в SDK нет).- Additive-only: новые optional-поля с default — без bump.
- Breaking —
ContractVersion = 2+ топикpin.profile.verification-requests.v2+ dual-publish обоих контрактов топика + deadline миграции consumers + sunset.v1. - Enum-полей в payload нет — unknown enum handling не применяется.
- Порядок per-user гарантирован key =
OwnerUserId(submitted и completed одного пользователя упорядочены между собой).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
Id, OwnerUserId, SubmittedAt | Internal | include |
Событие НЕ содержит PII (ФИО/дата рождения/паспортные данные — только в зашифрованном SnapshotCipher БД
profile), ссылок на сканы, secrets, tokens. Доступ к топику — m2m ACL Kafka (notifications, support).
Уведомления потребителей не должны включать значения данных заявки — только факт и ссылку на ЛК/admin.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: VerificationRequestEntity
- API (4): GET /api/v1/profile/verification — Моя заявка на верификацию (getMyVerification), POST /api/v1/profile/verification — Создание черновика заявки KYC (createVerificationRequest), POST /api/v1/profile/verification/{requestId}/submit — Отправка заявки KYC на ревью (submitVerification), PUT /api/v1/profile/verification/{requestId} — Правка черновика заявки KYC (updateVerificationDraft)
- Use Cases (1): UC-PRF-003. Подача заявки на верификацию инвестора и её одобрение (KYC)
- События (1): Event Contract: pin.profile.verification-completed (VerificationCompletedKafkaEvent)
- Консюмеры (1): Consumer: Profile Events → Notifications (notifications-profile-events)
- registries (1): Реестр событий PIN
- index.md (1): Profile Service (Pin.Profile)
Связанные документы
- Сущность (дом полей, инварианты, stateDiagram): verification-request.md;
денормализация статуса: profile.md (
ProfileVerificationStatus). - Соседний контракт того же топика: verification-completed.md.
- Источник (операция):
submitVerification— карта API в README; admin-очередь ревью —listVerificationQueue/takeVerificationInReview(там же). - Решения: ADR-0052 (инвестор вне tenant), ADR-0056 (simplicity-first).