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

UC-HND-001. Администратор поддерживает справочник (archive/restore)

Назначение

Описывает сценарий поддержки справочника администратором платформы: по жалобе на устаревшее значение или по reconciliation-отчёту после полной пересинхронизации администратор проверяет состояние синка, выводит запись из оборота (archive) — значение перестаёт предлагаться в lookup-выдачах, но все существующие ссылки в сервисах остаются валидными (resolve отдаёт архивную запись), — и возвращает её (restore) при ошибке. Связан с контекстом handbook.

Это единственный ручной write-путь справочника: создание и изменение реплика-полей — только синк источника (INV-HND-1, ../domain/country.md); физических удалений нет вообще — архив компенсирует delete-gap источника (ADR-0063 п.5).

Metadata

ПолеЗначение
IDUC-HND-001
Контекстhandbook (см. docs/06-services/handbook/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольАдминистратор платформы
Permissions / scopehandbook.entries.manage в scope «Платформа»
Tenantне применяется — справочник не tenanted (платформенные данные)
Record-levelне применяется

Предусловие

  • Администратор аутентифицирован, имеет handbook.entries.manage в scope платформы.
  • Реплика справочника наполнена синком (записи существуют); consumer группы pin-handbook-talent-sync работает.
  • Для reconciliation-ветки: проведена полная пересинхронизация (Talent POST sync-handbooks) и сформирован reconciliation-отчёт — строки реплики, не пришедшие в окно ресинка, помечены кандидатами в архив (../background/talent-initial-import.md); автоматического архива по отсутствию в окне нет — решение всегда за администратором.

Триггер

ПолеЗначение
ИсточникЖалоба пользователя/поддержки («в фильтре городов предлагается несуществующий/дублирующий город») ИЛИ reconciliation-отчёт после пересинхронизации (delete-gap: запись удалена в источнике, но живёт в PIN)
Триггерящий элементАдмин-раздел «Справочники» — карточка записи / список кандидатов в архив

Основной Поток

  1. Сигнал. Администратор получает сигнал: жалоба на значение в lookup либо reconciliation-отчёт с кандидатами в архив.
  2. Проверка синка. Администратор открывает состояние синка — GET /api/v1/admin/handbook/sync-state (../api/admin/get-sync-state.md): чекпоинты HandbookSyncState (сводка по типам записей), лаг, DLQ-счётчики. Убеждается, что синк догнал источник — т.е. запись действительно больше не приходит из источника, а не просто отстаёт реплика (иначе — сначала разбор лага/DLQ, архив преждевременен).
  3. Сверка с источником. Администратор находит запись (публичный lookup / resolve по id) и сверяет с источником: записи в Talent Handbook больше нет (delete-gap) либо значение ошибочно/задублировано и до правки в источнике его нужно скрыть от пользователей.
  4. Вызов архива. Администратор вызывает архив — POST /api/v1/admin/handbook/{type}/{id}/archive (../api/admin/archive-restore-entry.md), где {type} ∈ {countries, regions, cities, currencies, languages}.
  5. Валидация и запись. Система проверяет существование записи (нет → 404 HANDBOOK_ENTRY_NOT_FOUND) и ставит IsArchived = true; повторный архив уже архивной записи — идемпотентный no-op (состояние не меняется, повторное событие не публикуется). Операция логируется с актором.
  6. Публикация события. В одном согласованном изменении с архивом re-publish-ится событие {x}-upserted в соответствующий топик pin.handbook.*.v1 (compacted, key = id, полный snapshot реплики с IsArchived = true).
  7. Эффект для платформы (INV-HND-5 LookupVisibility):
    • публичный lookup перестаёт предлагать значение — новые записи потребителей на него не ссылаются;
    • существующие ссылки остаются валидными: internal resolve (../api/internal/resolve-references.md) отдаёт архивную запись по id, snapshot-модели строятся и для архивной записи — исторические документы и события полны (../domain/snapshots.md п.5);
    • chessboard получает событие и помечает строку своей локальной реплики архивной — значение уходит из фильтров витрины (окно eventual-consistency — секунды).
  8. Проверка результата. Администратор проверяет результат: значения нет в lookup; точечный resolve по id по-прежнему возвращает запись (с признаком архива); в reconciliation-отчёте кандидат закрыт.

Альтернативные Потоки

A1. Ошибочный архив — restore

  1. Обнаружение ошибки. Администратор обнаруживает, что заархивировал не ту запись (жалоба «пропал город из фильтра» / собственная проверка), и вызывает POST /api/v1/admin/handbook/{type}/{id}/restore.
  2. Проверка перед restore. Система проверяет: запись существует (нет → 404 HANDBOOK_ENTRY_NOT_FOUND); для записей с ISO-кодами (страны, валюты, языки) — коды свободны среди неархивных (INV-HND-2); конфликт → 409 HANDBOOK_SYNC_ISO_CONFLICT, restore отклоняется (см. A3).
  3. Восстановление. IsArchived = false; повторный restore неархивной записи — идемпотентный no-op. Re-publish {x}-upserted с IsArchived = false — запись снова в lookup, реплика chessboard догоняет.

Завершается — состояние справочника возвращено.

A2. Запись снова приходит из источника — авто-restore

  1. Повторный upsert. После архива запись вновь приходит upsert-событием из Talent (запись не была удалена в источнике — архив был преждевременным, либо её завели заново).
  2. Авто-restore. Синк применяет upsert поверх архивной записи: реплика-поля перезаписываются (snapshot-семантика), IsArchived автоматически сбрасывается в false + warn-метрика — источник главнее локального архива (правила ../domain/talent-replica.md). Re-publish события.
  3. Сигнал администратору. Администратор видит warn на дашборде handbook-sync-overview. Если скрытие значения всё же требуется — вопрос решается правкой данных в Talent Handbook (записи там больше не будет — и авто-restore не повторится), а не повторным архивом в PIN.

A3. Restore при ISO-конфликте

Restore отклонён — конфликт ISO-кодов
  1. За время архива источник завёл новую запись с теми же ISO-кодами — уникальность среди неархивных (INV-HND-2) теперь занята.
  2. Restore отклоняется: 409 HANDBOOK_SYNC_ISO_CONFLICT. Автопочинки на стороне PIN нет — разрешение конфликта (какая из записей легитимна) выполняется правкой данных в источнике; после прихода исправляющих событий restore повторяется либо становится ненужным.

A4. Синк не догнал источник

Архив откладывается до синхронизации
  1. На шаге 2 sync-state показывает лаг или непустой DLQ по нужному потоку — вывод «запись не приходит из источника» сделать нельзя.
  2. Администратор сначала разбирает лаг/DLQ (runbook ../consumers/talent-handbook-sync.md); архив откладывается. Возвращается к шагу 2 после того, как синк догнал.

Постусловие

  • Запись переведена в целевое состояние: IsArchived = true (архив) или false (restore/авто-restore); физически ничего не удалено.
  • Соответствующее событие {x}-upserted опубликовано в pin.handbook.*.v1; локальная реплика chessboard согласована асинхронно (секунды).
  • Публичный lookup отражает состояние (архивная запись скрыта / восстановленная видна); internal resolve отдаёт запись по id в любом состоянии — все исторические ссылки (CountryId/RegionId/CityId/ CurrencyId/LanguageId в документах и событиях сервисов) остаются валидными.
  • Операция администратора зафиксирована в аудите с актором и временем.

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
403 FORBIDDEN403Нет handbook.entries.manage в scope платформыСкрыть admin-операции справочника
HANDBOOK_ENTRY_NOT_FOUND404Archive/restore несуществующей записи (неверный id или тип справочника)Проверить id и справочник, обновить список
HANDBOOK_SYNC_ISO_CONFLICT409Restore: ISO-код записи занят другой неархивной записью (INV-HND-2, поток A3)Показать конфликтующую запись; разрешение — правка данных в источнике

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
country-upserted / region-upserted / city-upserted / currency-upserted / language-upsertedpin.handbook.{countries|regions|cities|currencies|languages}.v1 (compacted, key = id, ContractVersion 1)Archive/restore администратором (шаг 6, A1); авто-restore синком (A2) — payload = полный snapshot реплики с актуальным IsArchivedchessboard — локальная реплика (архив/возврат значения в фильтрах витрины)

Acceptance Criteria

#КритерийПроверка
AC1Given неархивная запись, when архив, then запись исчезает из публичного lookup, resolve отдаёт её с признаком архива, событие с IsArchived = true опубликованоархив города → lookup городов без него; resolve по id → запись есть; контракт-тест события
AC2Given архивная запись, when повторный archive, then идемпотентный no-op — состояние и версия не меняются, повторное событие не публикуетсядвойной archive → одно событие в топике
AC3Given архивная запись, when restore без ISO-конфликта, then запись снова в lookup, событие с IsArchived = false опубликованоrestore страны → lookup содержит её
AC4Given архивная запись, when restore при занятых ISO-кодах, then 409 HANDBOOK_SYNC_ISO_CONFLICT, состояние не меняетсяархив валюты → синк завёл новую с тем же IsoCode → restore → 409
AC5Given архивная запись, when из источника приходит её upsert, then авто-restore: IsArchived = false, warn-метрика, событие опубликованоэмуляция upsert-события источника поверх архивной записи
AC6Существующие ссылки переживают архив: документ/событие потребителя с id архивной записи полны — snapshot строится, resolve резолвитbatch-resolve со смесью активных/архивных/несуществующих id: архивные в ответе, несуществующие отсутствуют
AC7Без handbook.entries.manage archive/restore недоступны; выполненная операция зафиксирована в аудите с акторомзапрос без permission → 403; проверка записи аудита
AC8Sync-state показывает чекпоинты по типам записей, лаг и DLQ — администратор может отличить «запись не приходит» от «синк отстал» (поток A4)остановка consumer → sync-state показывает лаг

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

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

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