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

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.

Metadata

ПолеЗначение
Сервис/контекстprofile
API areaadmin (сотрудники ПИН)
Feature groupverification
Статусdraft
Документdocs/06-services/profile/api/admin/list-verification-queue.md

Актор и доступ

ПроверкаЗначение
Actor typeOperator(Admin) — сотрудник ПИН (ревьюер KYC), Bearer JWT
Auth policy / Permissionsauthenticated; платформенный permission profile.verification.review (permission.md)
Scope (RBAC)платформа (вне tenant); кросс-доступ к заявкам всех инвесторов по review-permission
Record-levelне применяется — очередь принципиально кросс-владельческая (не фильтруется по OwnerUserId); доступ гейтит только profile.verification.review
Tenant isolationне применяется (ADR-0052: данные инвестора вне tenant; заявки не tenanted)
Auditread: не аудируется per-record — снапшот не расшифровывается, наружу идут только метаданные очереди; аудит расшифровки — на операции viewVerificationSnapshot (VerificationSnapshotAccessedEvent)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/admin/profile/verification-queue
Success status200
Idempotency headerне применяется (безопасный read)
CorrelationOpenTelemetry trace (traceparent)

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ReviewerUserIdidentity claims (sub)GuidДавалидный JWT; NotEmpty401 без токена; используется для reviewerFilter = Mine
StatusqueryVerificationQueueStatusFilterНет (default Submitted)enum Submitted / InReview; строкой; unknown → ValidationError
ReviewerFilterqueryQueueReviewerScopeНет (default All)enum All / Mine; применим только при Status = InReview (иначе игнорируется — у Submitted нет ревьюера)
PagequeryintНет (default 1)>= 1Skip = (Page-1)*Size
SizequeryintНет (default 20)1..100; иначе ValidationError

Модель ответа — постраничный список VerificationQueueItemModel

Постраничный ответ: Items (строки очереди) + Meta (Total, Take, Offset, HasMore).

Элемент VerificationQueueItemModel

Дом полей заявки — verification-request.md; здесь проекция (дельта) без чувствительных значений снапшота (ФИО/паспорт не раскрываются до открытия карточки).

ПолеТипОбязательностьИсточникMaskingОписание
requestIdGuidДаVerificationRequestEntity.IdИдентификатор заявки (для take/view).
ownerUserIdGuidДаVerificationRequestEntity.OwnerUserIdВерифицируемая учётная запись (внутренний id, не PII).
submittedAtDateTimeOffsetДаVerificationRequestEntity.SubmittedAtМомент отправки; ключ FIFO-сортировки.
reviewStartedAtDateTimeOffset?НетVerificationRequestEntity.ReviewStartedAtnull для Submitted; момент взятия для InReview.
reviewerUserIdGuid?НетVerificationRequestEntity.ReviewerUserIdnull для Submitted; сотрудник, взявший заявку.
statusVerificationRequestStatusДаVerificationRequestEntity.StatusSubmitted / InReview (в выборке очереди только эти два).
waitingHoursintДаcomputed now - SubmittedAt (часы)Возраст ожидания для UI-триажа/SLA (заявки старше 48 ч — alert по README.md).

Алгоритм

  1. Контекст и доступ. Операцию выполняет ревьюер ПИН (ReviewerUserId = sub); гейт доступа — permission profile.verification.review (нет → 403). Tenant/scope не применяется (платформенная операция, ADR-0052).
  2. Проверка входа. ReviewerUserId не пустой; Page >= 1; Size ∈ [1..100]; Status/ReviewerFilter — известные enum-значения — иначе ValidationError.
  3. Что читаем. Строим выборку заявок по фильтру:
    • 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 не читаются и не расшифровываются.
  4. Маппинг. Строки заявок проецируются в VerificationQueueItemModel (таблица выше); waitingHours = (now - SubmittedAt) в часах (округление вниз). ФИО/паспорт на уровне очереди не раскрываются — только через viewVerificationSnapshot.
  5. Результат. Постраничный ответ: 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_FORBIDDEN403Нет permission profile.verification.review (не ревьюер ПИН)«Недостаточно прав для очереди ревью»no retry
ValidationError400Page < 1, Size вне 1..100, неизвестный Status/ReviewerFilterRestApiValidationErrorResponseno 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).

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

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

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

  • Сущности: verification-request.md (машина состояний, индексы очереди, SnapshotCipher/ScanList), profile.md (денормализация VerificationStatus).
  • Соседние admin-операции ревью (будущие): take-verification-in-review.md, approve-verification.md, reject-verification.md, view-verification-snapshot.md (аудируемая расшифровка).
  • Self-view инвестора: get-my-verification.md; permission — permission.md (profile.verification.review).
  • Сценарий: UC-PRF-003; SDK — docs/11-backend-sdk/.