GET /api/v1/internal/profile/verification-status/{userId} — Статус верификации для m2m (getVerificationStatus)
Internal m2m-запрос: по userId отдаёт денормализованный KYC-статус аккаунта из ProfileEntity
(поле VerificationStatus, дом — profile.md). Синхронная альтернатива событию
VerificationCompletedKafkaEvent (../events/verification-completed.md):
потребители (crm, catalog) держат признак «верифицирован» на своей реплике по событию, а этот endpoint
используют для холодного старта, сверки и точечного дочитывания, когда реплики ещё нет. Снапшот заявки и
любые PII не отдаются (расшифровка снапшота — только аудируемая admin-операция ревью,
verification-request.md).
Назначение
Дать m2m-потребителям (crm — бейдж «верифицированный инвестор» на карточке клиента/сделки; catalog —
гейтинг функций для верифицированных инвесторов) синхронно читать актуальный статус KYC одного аккаунта без
подписки на Kafka. Owning-контекст — profile, feature group verification. Данные — денормализованное
поле ProfileEntity.VerificationStatus, обновляемое в той же транзакции, что и решение по заявке
(INV-PRF-6, profile.md), поэтому синхронный ответ согласован с событием.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | profile |
| API area | internal (m2m; потребители — crm, catalog) |
| Feature group | verification |
| Статус | draft |
| Документ | docs/06-services/profile/api/internal/get-verification-status.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | ServiceAccount (m2m internal; вызывающие сервисы — crm, catalog) |
| Auth policy / Permissions | m2m internal (service token / gateway mTLS); scope internal; permission profile.verification.read-internal (m2m-scope токена, permission.md) |
| Scope (RBAC) | не применяется (актор вне tenant; данные инвестора вне tenant, ADR-0052) |
| Record-level | изоляция по userId из маршрута; проверку прав на конкретного инвестора выполняет вызывающий сервис (delegated) — статус верификации не является чувствительными данными |
| Tenant isolation | не применяется (ADR-0052; tenant-интерсепторы не подключаются) |
| Audit | read: не аудируется — наружу идёт только денормализованный статус (без PII, без расшифровки SnapshotCipher) |
IUserActor(sub) self-scope здесь не применяется: userId приходит маршрутом (доверенный m2m-вызов),
а не из JWT actor-context, как в public-методах ЛК.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/internal/profile/verification-status/{userId} |
| Success status | 200 |
| Idempotency header | не применяется (GET — safe/idempotent по природе) |
| Correlation | OpenTelemetry trace (traceparent) |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | route {userId} | Guid | Да | != Guid.Empty → иначе ValidationError; существование в identity не проверяется (чужой контекст) | record-level по владельцу; авторизацию на конкретного инвестора делает вызывающий сервис (delegated) |
Модель ответа VerificationStatusModel
Дом полей — profile.md (ProfileVerificationStatus, INV-PRF-6); здесь только
проекция (дельта) денормализованного статуса, без чувствительных значений снапшота.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
userId | Guid | Да | ProfileEntity.Id (= UserId) | — | Эхо запрошенного аккаунта. |
verificationStatus | ProfileVerificationStatus | Да | ProfileEntity.VerificationStatus | — | NotVerified / Pending / Verified / Rejected (enum ниже). |
verifiedAt | DateTimeOffset? | Нет | ProfileEntity.VerifiedAt | — | Момент одобрения последней заявки; null при verificationStatus != Verified. |
Enum ProfileVerificationStatus
Значения идентичны profile.md (.HasConversion<string>() на стороне контракта):
| Значение | Условие | Как трактует потребитель |
|---|---|---|
NotVerified | Верификация не проходилась (default). | Бейдж не показывать; функции для верифицированных закрыты. |
Pending | Есть заявка в Submitted/InReview. | Бейдж не показывать; можно показать «на проверке». |
Verified | Последняя заявка Approved. | Бейдж «верифицирован»; функции открыты (verifiedAt заполнен). |
Rejected | Последняя заявка Rejected. | Бейдж не показывать; функции закрыты. |
VerifiedRequestId и снапшот данных заявки наружу не отдаются (внутренняя ссылка/PII контекста
profile); связь с событием потребитель ведёт по VerificationCompletedKafkaEvent.Id собственной реплики.
Алгоритм
- Контекст и доступ. Вызывает m2m service-account (
crm/catalog), scopeinternal+ permissionprofile.verification.read-internal(иначе 401/403). Tenant не применяется (ADR-0052). - Проверка входа.
UserIdне пустой (!= Guid.Empty) — иначеValidationError. Состояние чтение не ограничивает: статус читается для любогоProfileStatus(в т.ч.Blocked/Archived) — гейтинг у потребителя, не здесь. - Что читаем. Профиль по
UserId; если профиль ещё не создан консюмеромpin.identity.users.v1(лаг реплики, consumers/identity-users-sync.md) →PROFILE_NOT_FOUND(вызывающий повторяет с backoff). Заявка и снапшот не читаются — статус берётся из денормализованного поля профиля. - Результат. Проекция
ProfileEntity → VerificationStatusModel:userId = profile.Id,verificationStatus = profile.VerificationStatus,verifiedAt = profile.VerifiedAt. Снапшот не расшифровывается. HTTP 200. Freshness:VerificationStatusденормализован и меняется в одном согласованном изменении с решением по заявке (INV-PRF-6), поэтому синхронный ответ согласован сVerificationCompletedKafkaEvent; потребитель может кэшировать значение (см. «Совместимость и наблюдаемость»).
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
UNAUTHENTICATED | 401 | Нет/просрочен m2m service token | без тела (ветка Other) | обновить service token; повтор |
FORBIDDEN | 403 | Токен без scope internal / permission profile.verification.read-internal | без тела | no retry (исправить m2m-credential) |
ValidationError | 400 | Пустой UserId в маршруте | RestApiValidationErrorResponse | no retry |
PROFILE_NOT_FOUND | 404 | Профиль ещё не создан консюмером identity (лаг реплики) | «Профиль не найден» | retry с backoff (1–5 с) |
Совместимость и наблюдаемость
- Новые optional поля ответа — additive, без breaking change; удаление/переименование/смена типа — breaking
(migration + rollout координируется с
crm/catalog). Unknown значениеverificationStatusу потребителя — fail closed: трактовать как «не верифицирован» (бейдж скрыть, функции не открывать). - Кэширование: значение стабильно между терминальными решениями KYC; потребителю рекомендован короткий TTL с
инвалидацией по
VerificationCompletedKafkaEvent(event-driven push + этот sync-read как reconciliation). - Logs:
UserId,verificationStatus, actor(service), latency (без PII); metrics:profile_api_operations_total{operation="getVerificationStatus",result}, latency p95 (SLO README — internal p99 < 200 мс); traces — OTel. Dashboardprofile-overview; runbook — replay DLQ identity-sync (при всплескеPROFILE_NOT_FOUND).
Acceptance Criteria
- Happy path. Given профиль существует, m2m-вызов
crm/catalogсprofile.verification.read-internal; When get; Then200,VerificationStatusModelс денормализованнымverificationStatusиverifiedAt. - Лаг реплики. Given профиль ещё не создан консюмером
pin.identity.users.v1; When get; Then404 PROFILE_NOT_FOUND; потребитель повторяет с backoff (1–5 с). - Нет прав m2m. Given токен без scope
internalили без permission; When get; Then403 FORBIDDEN; без токена —401 UNAUTHENTICATED. - Пустой id. Given
userId = Guid.Empty; When get; Then400 ValidationError. - Гейтинг у потребителя. Given профиль
Blocked/Archived; When get; Then статус всё равно отдаётся (200) — ограничение функций решает потребитель. - Синхронность с решением. Given заявка только что одобрена; When get сразу после; Then возвращается
Verifiedнемедленно (обновлено в той же транзакции, INV-PRF-6) — sync-read согласован сVerificationCompletedKafkaEventи служит reconciliation. - Неизвестный статус у клиента. Given будущее значение
verificationStatus; When старый потребитель разбирает ответ; Then трактует его консервативно как «не верифицирован» (бейдж скрыт, функции закрыты).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (1): Profile API — реестр операций областей public / internal / admin
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом статуса, INV-PRF-6, денормализация): profile.md; машина состояний
заявки и переходы
VerificationStatus— verification-request.md. - Асинхронная альтернатива (push): ../events/verification-completed.md
(потребители
crm/catalog); консюмер реплики профиля — ../consumers/identity-users-sync.md. - Соседний self-view того же статуса: get-my-verification.md, get-my-profile.md; соседний internal m2m-метод — get-requisite-snapshot.md (карта в README.md, область Internal).
- Permission
profile.verification.read-internal— permission.md; ADR-0052, ADR-0056.