Перейти к основному содержимому

Event Contract: pin.profile.verification-completed (VerificationCompletedKafkaEvent)

Версионируемый контракт интеграционного события «верификация инвестора завершена». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей — entity: VerificationRequestEntity.

Один контракт покрывает оба терминальных исхода KYC — Approved и Rejected (поле Status): отдельные события «approved»/«rejected» не заводятся, чтобы потребители обрабатывали итог единообразно и порядок per-user в общем топике сохранялся (решение зафиксировано в verification-request.md, раздел «Доменные и интеграционные события»).

Назначение

Фиксирует решение ревьюера ПИН по заявке KYC/AML: переход InReview → Approved либо InReview → Rejected (методы Approve/Reject агрегата, инвариант INV-VRF-8 DecisionByReviewer). В той же транзакции обновляется денормализованный Profile.VerificationStatus (Pending → Verified / Pending → Rejected, INV-PRF-6 в profile.md).

Публичное: downstream-контексты узнают итог без синхронного запроса в profilenotifications уведомляет инвестора, crm и catalog включают признак «верифицированный инвестор» (бейдж на карточке клиента, доступ к функциям для верифицированных).

Metadata

ПолеЗначение
Event typepin.profile.verification-completed
C# typeVerificationCompletedKafkaEvent
Topicpin.profile.verification-requests.v1 (общий с VerificationRequestSubmittedKafkaEvent; общий key = порядок per-user)
Partitions / key3 партиции (ADR-0056) / key = OwnerUserId
ContractVersionstatic int ContractVersion = 1
Producerprofile: admin-handlers approveVerification / rejectVerification (permission profile.verification.review)
Owning teamPIN Platform Core
Статусdraft

Метаданные события — идентификатор сообщения, время события CreatedAt, версия (= ContractVersion), payload, AggregateId = Id (заявка), AggregateType = "VerificationRequest". Trace — на outbox-записи. Событие публикуется через транзакционный outbox атомарно с решением по заявке и обновлением ProfileEntity (одно согласованное изменение: заявка + ProfileEntity + событие).

TenantId отсутствует: инвестор вне tenant (ADR-0052); изоляция record-level по OwnerUserId.

Payload VerificationCompletedKafkaEvent

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
IdGuidДаVerificationRequestEntity.IdImmutableИдентификатор заявки (= AggregateId); связь с ранее полученным verification-request-submitted.
OwnerUserIdGuidДаVerificationRequestEntity.OwnerUserIdImmutableВерифицированная/отклонённая учётная запись (= Kafka key).
Statusstring (enum VerificationRequestStatus, только терминальные)ДаVerificationRequestEntity.StatusAdditive only (новые значения)Approved — аккаунт верифицирован; Rejected — отказ. Другие значения enum в этом событии не встречаются.
RejectionCodestring? (enum VerificationRejectionCode)Нет (обязателен при Status = Rejected, null при Approved)VerificationRequestEntity.RejectionCodeAdditive only (новые значения)Машиночитаемая причина отказа: IllegibleScans / DataMismatch / DocumentExpired / SuspectedFraud / Other (семантика — verification-request.md).
CompletedAtDateTimeOffsetДаVerificationRequestEntity.CompletedAtImmutableМомент решения (Approved/Rejected).

Вложенных моделей нет. RejectionReason (человекочитаемое пояснение), DecisionComment (внутренний комментарий ревьюера), ReviewerUserId и снапшот данных в payload не входят: текст причины инвестор читает в ЛК (getMyVerification), комментарий и личность ревьюера — внутренние данные profile (аудит), PII не публикуется.

Когда публикуется

УсловиеПереход состоянияТранзакция
approveVerification (ревьюер, INV-VRF-8)заявка InReview → Approved; Profile.VerificationStatus: Pending → Verified (+VerifiedAt, VerifiedRequestId)заявка + ProfileEntity + событие в одном согласованном изменении
rejectVerification (ревьюер, RejectionCode + RejectionReason обязательны, INV-VRF-8)заявка InReview → Rejected; Profile.VerificationStatus: Pending → Rejectedта же

Оба статуса терминальны: по одному Id событие публикуется не более одного раза (после Rejected инвестор создаёт новую заявку с новым Id — INV-VRF-9 снят, цикл начинается с verification-request-submitted).

Потребители (consumers)

Consumer-контекстConsumerSide effectsИдемпотентностьЗадержка (SLA)
notificationsNotificationsVerificationCompletedConsumerУведомление инвестору: при Approved — «вы верифицированы»; при Rejected — «заявка отклонена, подробности в ЛК» (RejectionCode маппится на шаблон текста; RejectionReason в уведомление не попадает — только в ЛК)MessageId + business key Id (терминальность)< 5 мин
crmCrmVerificationCompletedConsumerФлаг «верифицированный инвестор» на реплике клиента (OwnerUserId): бейдж в карточке клиента/сделки для менеджеров и агентовMessageId + Id; при Rejected флаг не ставится< 5 мин
catalogCatalogVerificationCompletedConsumerПризнак верифицированности пользователя — доступ к функциям каталога для верифицированных инвесторовтот же< 5 мин

Consumer-документы — будущие пути docs/06-services/notifications/consumers/profile-verification-completed.md, docs/06-services/crm/consumers/profile-verification-completed.md, docs/06-services/catalog/consumers/profile-verification-completed.md (по будущим ТЗ; конвенция ссылок). Синхронная альтернатива для m2m — internal API getVerificationStatus(userId) (README, область Internal).

Доставка и восстановление

  • At-least-once через транзакционный outbox. Событие фиксируется в одном согласованном изменении с решением по заявке и обновлением ProfileEntity; при недоступности брокера решение ревьюера не теряется (сохранено в БД), публикация досылается ретраем. Потребители обязаны быть идемпотентными (возможны дубли).
  • Порядок per-user по key = OwnerUserId: completed приходит после submitted того же пользователя (общий топик, один ключ). Между пользователями порядок не гарантирован.
  • Терминальность и идемпотентность. По одному Id событие публикуется не более одного раза (оба статуса терминальны); дедуп потребителя — по MessageId + business key Id. Повторная доставка не должна повторно слать уведомление/повторно менять флаг верифицированности.
  • Poison-message. Необрабатываемое после ретраев событие (сбой БД потребителя) → DLQ контекста-потребителя
    • alert; статус в profile остаётся источником истины, восстановление — ре-плей из DLQ. Неизвестный Status в DLQ не отправляется — fail-closed (признак верифицированности не включается, см. ниже).

Версионирование и совместимость (реальность SDK)

  • ContractVersion = 1; при envelope.Version != ContractVersion — warn-and-proceed (upcasting в SDK нет).
  • Additive-only: новые optional-поля с default — без bump.
  • Breaking — ContractVersion = 2 + топик pin.profile.verification-requests.v2 + dual-publish обоих контрактов топика + deadline миграции + sunset .v1.
  • Unknown enum: Status — fail closed, вести себя как Rejected (признак верифицированности не включать), warning; RejectionCode — fallback Other, warning.
  • Порядок per-user гарантирован key = OwnerUserId (completed приходит после submitted того же пользователя).

Безопасность

ПолеКласс данныхПравило
Id, OwnerUserId, Status, CompletedAtInternalinclude
RejectionCodeInternalinclude; инвестору дословно не показывается — потребители маппят на локализованный шаблон, детали — в ЛК

Событие НЕ содержит PII (снапшот заявки — только шифротекст в БД profile), RejectionReason, DecisionComment, ReviewerUserId, secrets, tokens. Доступ к топику — m2m ACL Kafka (notifications/crm/catalog).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы

  • Сущность (машина состояний, инварианты INV-VRF-6..9): verification-request.md; денормализация: profile.md (ProfileVerificationStatus, INV-PRF-6).
  • Соседний контракт того же топика: verification-request-submitted.md.
  • Источники (операции): admin-review API approveVerification/rejectVerification — карта в README; permission profile.verification.reviewpermission.md.
  • Решения: ADR-0052 (инвестор вне tenant), ADR-0056 (simplicity-first).