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

POST /api/v1/admin/handbook/{type}/{id}/{archive,restore} — Архив и восстановление справочной записи

Назначение

Пара admin-команд над справочной записью любого из пяти типов: archive — вывести запись из оборота (ручной tombstone, закрывающий delete-gap источника: Talent Handbook удаляет записи жёстко и без событий — ADR-0063 п.5), restore — вернуть ошибочно заархивированную запись. Архивная запись исчезает из публичного lookup, но остаётся резолвимой по id (INV-HND-5) — исторические ссылки не ломаются. Это единственный write-путь администратора в handbook: реплика-поля правит только синк источника (INV-HND-1).

Metadata

ПолеЗначение
Сервис/контекстhandbook
API areaadmin (администратор платформы, ADR-0053)
Feature groupentries
Статусdraft
Документdocs/06-services/handbook/api/admin/archive-restore-entry.md

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

ПроверкаЗначение
Actor typeАдминистратор платформы через admin API — отдельная admin-система вне основной Identity (ADR-0053); m2m-токен admin-системы, audience pin.handbook.admin
Auth policy / Permissionshandbook.entries.manage
Scope (RBAC)платформа
Tenant isolationне применяется — справочники платформенные
Auditобязателен: X-Admin-User-Id (header) → актор фиксируется в логе каждой команды (запись/тип/действие/актор/результат)

HTTP-контракт

ПолеЗначение
MethodPOST (обе команды)
Routes/api/v1/admin/handbook/{type}/{id}/archive · /api/v1/admin/handbook/{type}/{id}/restore
Success status200 (в т.ч. идемпотентный no-op при повторном вызове)
Idempotency headerне применяется — идемпотентность естественная: повтор той же команды — no-op 200
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / Ограничения
typeroutestringДаcountries | regions | cities | currencies | languages; иначе VALIDATION_FAILED
idroutelongДачисло; для countries/currencies/languages — в диапазоне short; существование записи — на выборке (иначе 404)
AdminUserIdheader X-Admin-User-IdGuidДанепустой; используется для аудита

Модель ответа HandbookEntryStateModel

ПолеТипОбязательностьИсточникОписание
TypeHandbookAggregateTypeДаrouteТип записи (enum строкой: Country / Region / City / Currency / Language).
IdlongДазаписьИдентификатор записи (для short-типов — то же значение).
NamestringДазаписьDisplay-имя записи (для страны — FullName).
IsArchivedboolДазаписьИтоговое состояние после команды.
ChangedboolДавычисляетсяtrue — состояние фактически изменилось (событие опубликовано); false — идемпотентный no-op.
WarningHandbookRestoreWarning?НетвычисляетсяТолько restore: NotSeenInLastResync — запись не приходила из источника в окно последней полной пересинхронизации; вероятно, удалена в источнике, и ближайший reconciliation снова пометит её кандидатом в архив.

Алгоритм

  1. Контекст и доступ. Команду выполняет администратор платформы через admin API (permission handbook.entries.manage); X-Admin-User-Id обязателен — актор пишется в аудит-лог.
  2. Проверка входа. type ∈ пяти допустимым значениям; id — число допустимого для типа диапазона — иначе VALIDATION_FAILED (400).
  3. Существование. Запись типа type по id должна существовать (архивная — тоже «существует») — иначе HANDBOOK_ENTRY_NOT_FOUND (404).
  4. Идемпотентный no-op. Archive уже архивной записи / restore неархивной — 200 с Changed = false: состояние не меняется, событие не публикуется, аудит-лог фиксирует no-op.
  5. Restore: проверка ISO-конфликта (INV-HND-2). Для типов с ISO-кодами (страны, валюты, языки): если код (IsoCode2/IsoCode3/IsoCode) восстанавливаемой записи занят другой неархивной записью того же справочника — отказ HANDBOOK_SYNC_ISO_CONFLICT (409); разрешение — правка данных в источнике либо архив конкурирующей записи. Регионы/города ISO-кодов не имеют — проверка не применяется.
  6. Применение. IsArchived переключается (archive → true, restore → false), UpdatedAt обновляется; реплика-поля и версия (ReplicaVersion / LastEventAt региона) не меняются (INV-HND-1). Архив не каскадирует: архив страны не архивирует её регионы/города; согласованность витрин — забота потребителей (архивная страна исчезает из lookup, её города остаются резолвимыми).
  7. Re-publish. В одном согласованном изменении с записью публикуется событие {x}-upserted в соответствующий топик pin.handbook.*.v1 (полный snapshot реплики с новым IsArchived) — потребители (локальная реплика chessboard) узнают об изменении видимости.
  8. Предупреждение при restore. Если последнее событие источника по записи старше момента последней завершённой полной пересинхронизации (по HandbookSyncState, talent-replica.md) — записи в источнике, вероятно, уже нет: restore выполняется, но ответ несёт Warning = NotSeenInLastResync (следующий reconciliation снова предложит запись в архив — talent-initial-import.md).
  9. Результат. 200 + HandbookEntryStateModel.
Приоритет источника

Если после archive из Talent приходит upsert этой записи, синк выполняет auto-restore с warn-метрикой (источник главнее локального архива — ADR-0063 п.5); архив в PIN не блокирует синк.

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные события{country|region|city|currency|language}-upsertedpin.handbook.{countries|regions|cities|currencies|languages}.v1 (compacted, key = id, ContractVersion 1) — только при Changed = true; payload — полный snapshot реплики с новым IsArchived
Проекциипотребители топиков (локальная реплика chessboard) применяют новое состояние асинхронно (eventual consistency — секунды)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth/authz)401 / 403нет/невалиден токен admin-системы; нет handbook.entries.manageбез телаno retry
VALIDATION_FAILED400type вне пяти допустимых значений; id не число / вне диапазона типа; пустой X-Admin-User-Id«Проверьте параметры запроса»no retry
HANDBOOK_ENTRY_NOT_FOUND404записи типа type с таким id нет«Запись справочника не найдена»no retry
HANDBOOK_SYNC_ISO_CONFLICT409restore: ISO-код записи занят другой неархивной записью (INV-HND-2)«ISO-код занят активной записью»после разрешения конфликта

Acceptance Criteria

  • Archive. Given неархивный город; When POST /api/v1/admin/handbook/cities/{id}/archive; Then 200, IsArchived = true, Changed = true; город исчез из публичного lookup, но резолвится через internal resolve; в pin.handbook.cities.v1 опубликован city-upserted с IsArchived = true.
  • Повторный archive — no-op. Given уже архивный город; When archive повторно; Then 200, Changed = false, событие не публикуется.
  • Restore. Given архивный регион; When POST .../regions/{id}/restore; Then 200, IsArchived = false, Changed = true, re-publish region-upserted; регион снова в lookup.
  • Restore с ISO-конфликтом. Given архивная валюта с IsoCode = RUB и другая неархивная валюта с тем же кодом; When restore архивной; Then 409 HANDBOOK_SYNC_ISO_CONFLICT, состояние не изменилось, событие не публикуется.
  • Restore отсутствующей в источнике. Given запись заархивирована по итогам reconciliation и с тех пор из источника не приходила; When restore; Then 200, Changed = true, Warning = NotSeenInLastResync; запись восстановлена, но следующий reconciliation снова предложит её в архив.
  • Не найдено. Given id, которого нет в справочнике; When archive или restore; Then 404 HANDBOOK_ENTRY_NOT_FOUND.
  • Неизвестный тип. Given type = districts; When любая команда; Then 400 VALIDATION_FAILED.
  • Архив не каскадирует. Given страна с городами; When archive страны; Then города остались неархивными и резолвимыми; события опубликованы только по стране.
  • Источник главнее. Given запись заархивирована администратором; When из Talent приходит её upsert; Then синк восстанавливает запись (auto-restore) с warn-метрикой — команда архива не «прибивает» запись навсегда.
  • Аудит. Given любая выполненная команда (включая no-op); Then аудит-лог содержит тип, id, действие, X-Admin-User-Id, результат (Changed/ошибка).

Совместимость и наблюдаемость

  • Маршрут и семантика стабильны; расширение HandbookEntryStateModel (новые optional-поля, новые значения HandbookRestoreWarning) — additive; клиент admin-системы обязан игнорировать неизвестные значения Warning. Добавление нового type (новый справочник scope) — additive.
  • Событийный эффект — контракт: потребители pin.handbook.* вправе полагаться, что каждое фактическое изменение IsArchived доезжает событием (at-least-once, порядок per-key).
  • Logs: аудит каждой команды (см. AC); metrics: handbook_admin_entry_commands_total{type,action=archive|restore,outcome=changed|noop|conflict|not_found}; traces — OTel (traceparent); dashboard handbook-overview; alert не требуется (ручные редкие операции).

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

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

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