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 area | admin (администратор платформы, ADR-0053) |
| Feature group | entries |
| Статус | 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 / Permissions | handbook.entries.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | не применяется — справочники платформенные |
| Audit | обязателен: X-Admin-User-Id (header) → актор фиксируется в логе каждой команды (запись/тип/действие/актор/результат) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST (обе команды) |
| Routes | /api/v1/admin/handbook/{type}/{id}/archive · /api/v1/admin/handbook/{type}/{id}/restore |
| Success status | 200 (в т.ч. идемпотентный no-op при повторном вызове) |
| Idempotency header | не применяется — идемпотентность естественная: повтор той же команды — no-op 200 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения |
|---|---|---|---|---|
type | route | string | Да | ∈ countries | regions | cities | currencies | languages; иначе VALIDATION_FAILED |
id | route | long | Да | число; для countries/currencies/languages — в диапазоне short; существование записи — на выборке (иначе 404) |
AdminUserId | header X-Admin-User-Id | Guid | Да | непустой; используется для аудита |
Модель ответа HandbookEntryStateModel
| Поле | Тип | Обязательность | Источник | Описание |
|---|---|---|---|---|
Type | HandbookAggregateType | Да | route | Тип записи (enum строкой: Country / Region / City / Currency / Language). |
Id | long | Да | запись | Идентификатор записи (для short-типов — то же значение). |
Name | string | Да | запись | Display-имя записи (для страны — FullName). |
IsArchived | bool | Да | запись | Итоговое состояние после команды. |
Changed | bool | Да | вычисляется | true — состояние фактически изменилось (событие опубликовано); false — идемпотентный no-op. |
Warning | HandbookRestoreWarning? | Нет | вычисляется | Только restore: NotSeenInLastResync — запись не приходила из источника в окно последней полной пересинхронизации; вероятно, удалена в источнике, и ближайший reconciliation снова пометит её кандидатом в архив. |
Алгоритм
- Контекст и доступ. Команду выполняет администратор платформы через admin API (permission
handbook.entries.manage);X-Admin-User-Idобязателен — актор пишется в аудит-лог. - Проверка входа.
type∈ пяти допустимым значениям;id— число допустимого для типа диапазона — иначеVALIDATION_FAILED(400). - Существование. Запись типа
typeпоidдолжна существовать (архивная — тоже «существует») — иначеHANDBOOK_ENTRY_NOT_FOUND(404). - Идемпотентный no-op. Archive уже архивной записи / restore неархивной —
200сChanged = false: состояние не меняется, событие не публикуется, аудит-лог фиксирует no-op. - Restore: проверка ISO-конфликта (INV-HND-2). Для типов с ISO-кодами (страны, валюты, языки):
если код (
IsoCode2/IsoCode3/IsoCode) восстанавливаемой записи занят другой неархивной записью того же справочника — отказHANDBOOK_SYNC_ISO_CONFLICT(409); разрешение — правка данных в источнике либо архив конкурирующей записи. Регионы/города ISO-кодов не имеют — проверка не применяется. - Применение.
IsArchivedпереключается (archive → true,restore → false),UpdatedAtобновляется; реплика-поля и версия (ReplicaVersion/LastEventAtрегиона) не меняются (INV-HND-1). Архив не каскадирует: архив страны не архивирует её регионы/города; согласованность витрин — забота потребителей (архивная страна исчезает из lookup, её города остаются резолвимыми). - Re-publish. В одном согласованном изменении с записью публикуется событие
{x}-upsertedв соответствующий топикpin.handbook.*.v1(полный snapshot реплики с новымIsArchived) — потребители (локальная репликаchessboard) узнают об изменении видимости. - Предупреждение при restore. Если последнее событие источника по записи старше момента последней
завершённой полной пересинхронизации (по
HandbookSyncState, talent-replica.md) — записи в источнике, вероятно, уже нет: restore выполняется, но ответ несётWarning = NotSeenInLastResync(следующий reconciliation снова предложит запись в архив — talent-initial-import.md). - Результат.
200+HandbookEntryStateModel.
Приоритет источника
Если после archive из Talent приходит upsert этой записи, синк выполняет auto-restore с warn-метрикой (источник главнее локального архива — ADR-0063 п.5); архив в PIN не блокирует синк.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | {country|region|city|currency|language}-upserted → pin.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_FAILED | 400 | type вне пяти допустимых значений; id не число / вне диапазона типа; пустой X-Admin-User-Id | «Проверьте параметры запроса» | no retry |
HANDBOOK_ENTRY_NOT_FOUND | 404 | записи типа type с таким id нет | «Запись справочника не найдена» | no retry |
HANDBOOK_SYNC_ISO_CONFLICT | 409 | restore: ISO-код записи занят другой неархивной записью (INV-HND-2) | «ISO-код занят активной записью» | после разрешения конфликта |
Acceptance Criteria
- Archive. Given неархивный город; When
POST /api/v1/admin/handbook/cities/{id}/archive; Then200,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; Then200,IsArchived = false,Changed = true, re-publishregion-upserted; регион снова в lookup. - Restore с ISO-конфликтом. Given архивная валюта с
IsoCode = RUBи другая неархивная валюта с тем же кодом; When restore архивной; Then409 HANDBOOK_SYNC_ISO_CONFLICT, состояние не изменилось, событие не публикуется. - Restore отсутствующей в источнике. Given запись заархивирована по итогам reconciliation и с тех пор
из источника не приходила; When restore; Then
200,Changed = true,Warning = NotSeenInLastResync; запись восстановлена, но следующий reconciliation снова предложит её в архив. - Не найдено. Given
id, которого нет в справочнике; When archive или restore; Then404 HANDBOOK_ENTRY_NOT_FOUND. - Неизвестный тип. Given
type = districts; When любая команда; Then400 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); dashboardhandbook-overview; alert не требуется (ручные редкие операции).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity: CountryEntity
- API (3): GET /api/v1/admin/handbook/sync-state — Состояние синхронизации справочников с Talent, GET /api/v1/handbook/{countries,regions,cities,currencies,languages} — Публичный lookup справочников, Handbook API — реестр операций областей public / internal / admin
- Use Cases (2): UC-HND-001. Администратор поддерживает справочник (archive/restore), US-реестр контекста handbook (US-HND)
- События (5): Event Contract: city-upserted (CityUpsertedEvent), Event Contract: country-upserted (CountryUpsertedEvent), Event Contract: currency-upserted (CurrencyUpsertedEvent), Event Contract: language-upserted (LanguageUpsertedEvent), Event Contract: region-upserted (RegionUpsertedEvent)
- Фоновые задачи (1): Background Job: talent-initial-import (bootstrap и reconciliation справочников из Talent Handbook)
- index.md (1): Handbook Service
- registries (1): Реестр API-методов PIN
Связанные документы
- Дома полей и таблицы операций жизненного цикла: country.md (INV-HND-1/2/5), region.md, city.md, currency.md, language.md.
- События re-publish:
../../events/{country|region|city|currency|language}-upserted.md. - Reconciliation и полная пересинхронизация:
talent-initial-import.md; правила синка и
HandbookSyncState: talent-replica.md. - Состояние синка (диагностика перед archive/restore): get-sync-state.md.
- Use-case: UC-HND-001; ADR-0063 п.5, ADR-0053.