GET /api/v1/admin/profile/verification-queue — Очередь ревью KYC (listVerificationQueue)
Назначение
Вернуть ревьюеру ПИН FIFO-очередь заявок на верификацию инвестора (KYC/AML) в статусе Submitted,
отсортированную по SubmittedAt asc (кто раньше отправил — раньше в очереди), плюс опциональный вид «мои
взятые» (InReview с ReviewerUserId = текущий сотрудник) для продолжения работы. Отдаются только
метаданные очереди — расшифрованный снапшот (SnapshotCipher) не возвращается: ФИО и паспортные данные
раскрываются отдельной аудируемой операцией viewVerificationSnapshot при открытии карточки заявки
(verification-request.md). Первый шаг сценария ревью
UC-PRF-003; продолжение —
take-verification-in-review.md.
| Поле | Значение |
|---|
| Сервис/контекст | profile |
| API area | admin (сотрудники ПИН) |
| Feature group | verification |
| Статус | draft |
| Документ | docs/06-services/profile/api/admin/list-verification-queue.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Operator(Admin) — сотрудник ПИН (ревьюер KYC), Bearer JWT |
| Auth policy / Permissions | authenticated; платформенный permission profile.verification.review (permission.md) |
| Scope (RBAC) | платформа (вне tenant); кросс-доступ к заявкам всех инвесторов по review-permission |
| Record-level | не применяется — очередь принципиально кросс-владельческая (не фильтруется по OwnerUserId); доступ гейтит только profile.verification.review |
| Tenant isolation | не применяется (ADR-0052: данные инвестора вне tenant; заявки не tenanted) |
| Audit | read: не аудируется per-record — снапшот не расшифровывается, наружу идут только метаданные очереди; аудит расшифровки — на операции viewVerificationSnapshot (VerificationSnapshotAccessedEvent) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/admin/profile/verification-queue |
| Success status | 200 |
| Idempotency header | не применяется (безопасный read) |
| Correlation | OpenTelemetry trace (traceparent) |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ReviewerUserId | identity claims (sub) | Guid | Да | валидный JWT; NotEmpty | 401 без токена; используется для reviewerFilter = Mine |
Status | query | VerificationQueueStatusFilter | Нет (default Submitted) | enum Submitted / InReview; строкой; unknown → ValidationError | — |
ReviewerFilter | query | QueueReviewerScope | Нет (default All) | enum All / Mine; применим только при Status = InReview (иначе игнорируется — у Submitted нет ревьюера) | — |
Page | query | int | Нет (default 1) | >= 1 → Skip = (Page-1)*Size | — |
Size | query | int | Нет (default 20) | 1..100; иначе ValidationError | — |
Модель ответа — постраничный список VerificationQueueItemModel
Постраничный ответ: Items (строки очереди) + Meta (Total, Take, Offset, HasMore).
Элемент VerificationQueueItemModel
Дом полей заявки — verification-request.md; здесь проекция (дельта)
без чувствительных значений снапшота (ФИО/паспорт не раскрываются до открытия карточки).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
requestId | Guid | Да | VerificationRequestEntity.Id | — | Идентификатор заявки (для take/view). |
ownerUserId | Guid | Да | VerificationRequestEntity.OwnerUserId | — | Верифицируемая учётная запись (внутренний id, не PII). |
submittedAt | DateTimeOffset | Да | VerificationRequestEntity.SubmittedAt | — | Момент отправки; ключ FIFO-сортировки. |
reviewStartedAt | DateTimeOffset? | Нет | VerificationRequestEntity.ReviewStartedAt | — | null для Submitted; момент взятия для InReview. |
reviewerUserId | Guid? | Нет | VerificationRequestEntity.ReviewerUserId | — | null для Submitted; сотрудник, взявший заявку. |
status | VerificationRequestStatus | Да | VerificationRequestEntity.Status | — | Submitted / InReview (в выборке очереди только эти два). |
waitingHours | int | Да | computed now - SubmittedAt (часы) | — | Возраст ожидания для UI-триажа/SLA (заявки старше 48 ч — alert по README.md). |
Алгоритм
- Контекст и доступ. Операцию выполняет ревьюер ПИН (
ReviewerUserId = sub); гейт доступа —
permission profile.verification.review (нет → 403). Tenant/scope не применяется (платформенная
операция, ADR-0052).
- Проверка входа.
ReviewerUserId не пустой; Page >= 1; Size ∈ [1..100]; Status/ReviewerFilter —
известные enum-значения — иначе ValidationError.
- Что читаем. Строим выборку заявок по фильтру:
Status = Submitted (default) — не взятые заявки (ReviewerUserId == null); ReviewerFilter
игнорируется. Индекс очереди — ix_verification_requests_submitted_at.
Status = InReview — взятые в работу; при ReviewerFilter = Mine — только «мои взятые»
(ReviewerUserId = актор, индекс ix_verification_requests_reviewer_user_id); при All — все взятые
(супервайзерский вид).
Результат — страница Size записей со смещением (Page-1)*Size, отсортированная по SubmittedAt asc
(FIFO). Загружается только строка заявки: Person, документы и SnapshotCipher не читаются и не
расшифровываются.
- Маппинг. Строки заявок проецируются в
VerificationQueueItemModel (таблица выше);
waitingHours = (now - SubmittedAt) в часах (округление вниз). ФИО/паспорт на уровне очереди не
раскрываются — только через viewVerificationSnapshot.
- Результат. Постраничный ответ:
Items (спроецированные строки) + Meta { Total, Take = Size, Offset, HasMore }; порядок гарантирован SubmittedAt asc (FIFO). HTTP 200. Отсутствие permission — 403, пустая
очередь — 200 с пустым Items.
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
| — (auth) | 401 | Нет/просрочен JWT | без тела | re-login |
PROFILE_VERIFICATION_REVIEW_FORBIDDEN | 403 | Нет permission profile.verification.review (не ревьюер ПИН) | «Недостаточно прав для очереди ревью» | no retry |
ValidationError | 400 | Page < 1, Size вне 1..100, неизвестный Status/ReviewerFilter | RestApiValidationErrorResponse | no retry |
Совместимость и наблюдаемость
- Новые optional query-параметры и поля
VerificationQueueItemModel — additive, без breaking change;
удаление/переименование/смена типа или orderBy — breaking (migration + rollout). Unknown значение
status у клиента — трактовать консервативно (скрыть строку/действия).
- Logs:
ReviewerUserId, Status, ReviewerFilter, Meta.Total, latency (без ownerUserId-россыпи и
без каких-либо PII); metrics: profile_api_operations_total{operation="listVerificationQueue",result},
бизнес-gauge profile_verification_queue_depth{status} и profile_verification_oldest_submitted_hours
(alert: заявки в Submitted старше 48 ч — README.md); traces — OTel.
Dashboard profile-overview.
Acceptance Criteria
- FIFO-очередь. Given ревьюер с
profile.verification.review и N заявок Submitted; When list без параметров; Then 200, страница отсортирована SubmittedAt asc, только метаданные (VerificationQueueItemModel), снапшот не расшифрован.
- Мои взятые. Given
Status = InReview, ReviewerFilter = Mine; When list; Then возвращаются только заявки с ReviewerUserId = актор.
- Фильтр ревьюера на Submitted. Given
Status = Submitted, ReviewerFilter = Mine; When list; Then ReviewerFilter игнорируется (у Submitted нет ревьюера).
- Пустая очередь. Given нет заявок под фильтр; When list; Then
200 с пустым Items и Meta.Total = 0.
- Валидация пагинации/enum. Given
Page < 1, Size вне 1..100 или неизвестный Status/ReviewerFilter; When list; Then 400 ValidationError.
- Нет прав. Given актор без
profile.verification.review; When list; Then 403 PROFILE_VERIFICATION_REVIEW_FORBIDDEN.
- SLA-возраст. Given заявка ожидает > 48 ч; When list; Then её
waitingHours > 48, что отражается в gauge profile_verification_oldest_submitted_hours (alert README).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы