GET /api/v1/admin/handbook/sync-state — Состояние синхронизации справочников с Talent
Назначение
Отдаёт администратору платформы диагностическую сводку синка справочников из Talent Handbook: по каждому
типу записей — счётчики чекпоинтов HandbookSyncState и лаг, списком — проблемные записи (Conflict /
DeadLettered, требующие ручного действия), плюс момент последней завершённой полной пересинхронизации.
Основной инструмент расследования DLQ-инцидентов и решения «нужен ли ресинк / archive» перед командами
archive-restore-entry.md.
| Поле | Значение |
|---|
| Сервис/контекст | handbook |
| API area | admin (администратор платформы, ADR-0053) |
| Feature group | sync |
| Статус | draft |
| Документ | docs/06-services/handbook/api/admin/get-sync-state.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Администратор платформы через admin API — отдельная admin-система вне основной Identity (ADR-0053); m2m-токен admin-системы, audience pin.handbook.admin |
| Auth policy / Permissions | handbook.entries.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | не применяется |
| Audit | read (не sensitive) — служебные данные синка, PII нет; access-audit не пишется |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/admin/handbook/sync-state?aggregateType=&status= |
| Success status | 200 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения |
|---|
aggregateType | query | HandbookAggregateType? | Нет | enum строкой: Country / Region / City / Currency / Language; сужает сводку и список до одного типа; неизвестное значение → VALIDATION_FAILED |
status | query | HandbookSyncRecordStatus? | Нет | enum строкой: Synced / Conflict / DeadLettered; сужает список проблемных записей; неизвестное значение → VALIDATION_FAILED |
Модель ответа HandbookSyncStateSummaryModel
Источник — служебная сущность HandbookSyncState (чекпоинт «одна строка на запись источника», таблица
handbook.talent_sync_states — дом полей talent-replica.md) и отметка
прогонов полной пересинхронизации (talent-initial-import.md).
| Поле | Тип | Обязательность | Источник | Описание |
|---|
Aggregates | HandbookSyncAggregateSummary[] | Да | агрегация чекпоинтов | Сводка по типам (все пять либо один — по фильтру aggregateType). |
Records | HandbookSyncRecordModel[] | Да (может быть пустым) | чекпоинты | Проблемные записи: по умолчанию — Conflict и DeadLettered; фильтр status сужает до одного статуса. status = Synced списком не выдаётся (тысячи строк) — только сводкой. Порядок — UpdatedAt убыв.; не более 500 строк. |
LastFullResyncAt | DateTimeOffset? | Нет | отметка джобы пересинхронизации | Момент последнего успешно завершённого прогона полной пересинхронизации; null — не выполнялась. Точка отсчёта reconciliation и предупреждения NotSeenInLastResync при restore. |
DlqTopic | string | Да | константа контекста | pin.handbook.talent-sync-dlq.v1 — куда смотреть при DeadLettered. |
HandbookSyncAggregateSummary
| Поле | Тип | Обязательность | Описание |
|---|
AggregateType | HandbookAggregateType | Да | Тип записей источника. |
TotalCount | int | Да | Всего чекпоинтов типа (= записей реплики, прошедших через синк). |
SyncedCount | int | Да | В статусе Synced. |
ConflictCount | int | Да | В статусе Conflict (ISO-конфликты и пр.) — требуют ручного разрешения. |
DeadLetteredCount | int | Да | В статусе DeadLettered — событие в DLQ, требуется расследование/replay. |
LastEventAt | DateTimeOffset? | Нет | OccurredAt последнего применённого события типа; null — событий не было. |
LagSeconds | long? | Нет | now − LastEventAt. Для редко меняющихся справочников большой «лаг» сам по себе не проблема — сигналом являются ConflictCount/DeadLetteredCount и метрики consumer-а. |
HandbookSyncRecordModel
Проекция чекпоинта HandbookSyncState; дом полей — talent-replica.md.
| Поле | Тип | Обязательность | Описание |
|---|
AggregateType | HandbookAggregateType | Да | Тип записи. |
ExternalId | long | Да | Id записи в источнике (= id реплики; для short-типов — то же значение). |
Status | HandbookSyncRecordStatus | Да | Synced / Conflict / DeadLettered. |
LastAppliedVersion | long? | Нет | Последняя применённая версия источника; у регионов null — монотонность по времени события, версии у источника нет (region.md). |
LastEventAt | DateTimeOffset? | Нет | OccurredAt последнего применённого события записи. |
LastError | string? | Нет | Текст последней ошибки (например, HANDBOOK_SYNC_ISO_CONFLICT, HANDBOOK_SYNC_GEO_MISMATCH, HANDBOOK_SYNC_COUNTRY_NOT_FOUND после исчерпания ретраев); без PII. |
UpdatedAt | DateTimeOffset | Да | Момент последнего изменения чекпоинта. |
Алгоритм
- Контекст и доступ. Операцию выполняет администратор платформы через admin API (permission
handbook.entries.manage); read-only, без побочных эффектов.
- Проверка входа.
aggregateType и status, если переданы, — из допустимых enum-значений; иначе
VALIDATION_FAILED (400).
- Агрегация по типам. Чекпоинты
HandbookSyncState группируются по типу (все пять либо один — по
фильтру aggregateType) со счётчиками TotalCount/SyncedCount/ConflictCount/DeadLetteredCount,
LastEventAt, LagSeconds.
- Список проблемных записей. Отбираются записи в статусе
Conflict/DeadLettered (либо один статус —
по фильтру status; Synced в списке не выдаётся, только в счётчиках), не более 500 строк, порядок —
UpdatedAt убыв.
- Пересинхронизация. К ответу прикладывается
LastFullResyncAt — момент последнего успешно
завершённого прогона полной пересинхронизации.
- Результат.
200 + HandbookSyncStateSummaryModel; пустые сводка/список — валидный ответ (синк ещё
не выполнялся).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
| — (auth/authz) | 401 / 403 | нет/невалиден токен admin-системы; нет handbook.entries.manage | без тела | no retry |
VALIDATION_FAILED | 400 | aggregateType или status вне enum-значений | «Проверьте параметры запроса» | no retry |
Acceptance Criteria
- Полная сводка. Given работающий синк по пяти типам; When
GET /api/v1/admin/handbook/sync-state;
Then 200, Aggregates содержит пять строк со счётчиками Total/Synced/Conflict/DeadLettered,
LastEventAt/LagSeconds; Records — только Conflict/DeadLettered-записи.
- Фильтр по типу. Given
aggregateType=City; When запрос; Then сводка и записи — только по городам.
- Фильтр по статусу. Given
status=DeadLettered; When запрос; Then Records содержит только
DeadLettered-записи (с LastError), сводка не меняется.
- Synced списком не выдаётся. Given
status=Synced; When запрос; Then 200, Records = []
(данные о Synced — в счётчиках сводки).
- ISO-конфликт виден. Given событие валюты ушло в DLQ по
HANDBOOK_SYNC_ISO_CONFLICT; When запрос;
Then запись присутствует в Records со Status = DeadLettered и кодом в LastError;
DeadLetteredCount по валютам ≥ 1.
- Пересинхронизация. Given полная пересинхронизация успешно завершилась в момент T; When запрос;
Then
LastFullResyncAt = T; Given ни одной пересинхронизации — Then LastFullResyncAt = null.
- Регион без версии. Given чекпоинт региона; When запрос; Then
LastAppliedVersion = null,
LastEventAt заполнен.
- Неизвестный enum. Given
aggregateType=District; When запрос; Then 400 VALIDATION_FAILED.
Совместимость и наблюдаемость
- Расширения моделей сводки — additive (новые optional-поля, новые enum-значения
HandbookAggregateType
при расширении scope справочников — клиент admin-системы игнорирует неизвестные значения). Удаление/
переименование полей — breaking.
- Операция — витрина над теми же данными, что и метрики consumer-а
(talent-handbook-sync.md:
handbook_sync_lag_seconds, handbook_sync_dlq_total, handbook_sync_stale_events_total) — алерты живут
на метриках, API служит для расследования человеком.
- Logs: факт запроса (фильтры, размер выдачи); metrics:
handbook_admin_sync_state_requests_total,
latency p95; traces — OTel (traceparent); dashboard handbook-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы