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

POST /internal/handbook/resolve — Batch-резолв справочных записей по id

Назначение

Единственная internal-операция контекста handbook: батч-резолв справочных записей всех пяти типов по их id для внутренних сервисов PIN. Возвращает snapshot-модели (snapshots.md) с признаком IsArchivedвключая архивные записи (INV-HND-5): исторические ссылки не ломаются, а решение «допустима ли архивная/отсутствующая запись» принимает вызывающий. Основное применение — write-path-валидация справочных id (crm, profile, admin-write денежных контрактов) и fallback-резолв при отставании локальной реплики (chessboard).

Metadata

ПолеЗначение
Сервис/контекстhandbook
API areainternal (m2m)
Feature groupresolve
Статусdraft
Документdocs/06-services/handbook/api/internal/resolve-references.md

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

ПроверкаЗначение
Actor typeServiceAccount (m2m service token, client-credentials) — не user JWT
Auth policy / Permissionspolicy InternalServiceOnly; allowlist потребителей internal-области: chessboard, crm, profile, rewards, promotion, investor-owner; user-permissions не применяются
Scope (RBAC)internal (m2m)
Tenant isolationне применяется — справочники платформенные
Auditне пишется (не-PII данные); вызовы видны в метриках по потребителям

HTTP-контракт

ПолеЗначение
MethodPOST (тело — до 500 id; операция семантически read-only, безопасна для повтора)
Route/internal/handbook/resolve
Success status200 — всегда при валидном входе, независимо от того, сколько id найдено
Correlationсквозной trace через заголовок traceparent

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

Все списки опциональны; суммарное число id по всем спискам (после удаления дубликатов внутри каждого списка) — от 1 до 500. Дубликаты внутри списка допустимы и схлопываются без ошибки.

ПолеИсточникТипОбязательностьВалидация / Ограничения
CountryIdsbodyshort[]?Нетid стран
RegionIdsbodylong[]?Нетid регионов
CityIdsbodylong[]?Нетid городов
CurrencyIdsbodyshort[]?Нетid валют
LanguageIdsbodyshort[]?Нетid языков

Суммарно 0 id (все списки отсутствуют/пусты) → VALIDATION_FAILED (защита от бессмысленных вызовов при багах маппинга у потребителя); суммарно > 500 → VALIDATION_FAILED (вызывающий разбивает на батчи).

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

Пять коллекций — всегда присутствуют (пустые для незапрошенных типов). Элемент = snapshot-модель (snapshots.md) + IsArchived; для гео-типов дополнительно — ссылки на родителей (нужны гео-валидации потребителей: согласованность город ↔ регион ↔ страна). Неизвестный id просто отсутствует в соответствующей коллекции — ошибкой это не является.

ПолеТипОбязательностьОписание
CountriesResolvedCountry[]Да (может быть пустым)Найденные страны из CountryIds.
RegionsResolvedRegion[]ДаНайденные регионы из RegionIds.
CitiesResolvedCity[]ДаНайденные города из CityIds.
CurrenciesResolvedCurrency[]ДаНайденные валюты из CurrencyIds.
LanguagesResolvedLanguage[]ДаНайденные языки из LanguageIds.

ResolvedCountry

ПолеТипОбязательностьИсточникОписание
CountryIdshortДаCountrySnapshotИдентификатор страны.
IsoCode2stringДаCountrySnapshotISO 3166-1 alpha-2 — юридический след (документы profile).
NamestringДаCountrySnapshot (Name = FullName)Display-имя.
IsArchivedboolДаCountryEntity.IsArchivedЗапись выведена из оборота (в lookup не выдаётся).

ResolvedRegion

ПолеТипОбязательностьИсточникОписание
RegionIdlongДаRegionSnapshotИдентификатор региона.
NamestringДаRegionSnapshotНазвание региона.
CountryIdshortДаRegionEntity.CountryIdСтрана региона (сверка гео-цепочки потребителем).
IsArchivedboolДаRegionEntity.IsArchivedПризнак архива.

ResolvedCity

ПолеТипОбязательностьИсточникОписание
CityIdlongДаCitySnapshotИдентификатор города.
NamestringДаCitySnapshotНазвание города.
CountryIdshortДаCityEntity.CountryIdСтрана города.
RegionIdlong?НетCityEntity.RegionIdРегион города (null — город вне региона).
IsArchivedboolДаCityEntity.IsArchivedПризнак архива.

ResolvedCurrency

ПолеТипОбязательностьИсточникОписание
CurrencyIdshortДаCurrencySnapshotИдентификатор валюты.
IsoCodestringДаCurrencySnapshotКод ISO-4217 (RUB).
NamestringДаCurrencySnapshotНазвание валюты.
Signstring?НетCurrencySnapshotЗнак валюты.
IsArchivedboolДаCurrencyEntity.IsArchivedПризнак архива.

ResolvedLanguage

Платформенной snapshot-модели языка нет (потребителей у языков в PIN нет — language.md); состав resolve-элемента определяется здесь.

ПолеТипОбязательностьИсточникОписание
LanguageIdshortДаLanguageEntity.IdИдентификатор языка.
NamestringДа.NameНазвание языка.
IsoCode2stringДа.IsoCode2ISO 639-1.
IsoCode3stringДа.IsoCode3ISO 639-2/3.
IsArchivedboolДа.IsArchivedПризнак архива.

Потребители и их сценарии

ПотребительСценарий вызоваРеакция на отсутствие/архив
chessboardfallback-резолв при отставании локальной реплики (write-path проектов/цен)отсутствие → CHESSBOARD_GEO_REF_UNKNOWN; архив → CHESSBOARD_GEO_REF_ARCHIVED; рассогласование город↔регион → CHESSBOARD_CITY_REGION_MISMATCH (коды — контракты chessboard)
crmвалидация CityId пожеланий лида (update-lead-wishes)отсутствие → CRM_CITY_UNKNOWN
profileвалидация CountryId документов персон; IsoCode2 — для инвариантов документов и юридического следаотсутствие → PROFILE_COUNTRY_UNKNOWN
rewards, promotion, investor-ownerвалидация CurrencyId на admin-write денежных контрактовотсутствие/архив → отказ операции по правилам вызывающего
Архивные записи: история vs write-path

Резолв исторических ссылок (чтение старых документов/событий) допускает IsArchived = true без отказа; на write-path потребители, как правило, отклоняют и отсутствующие, и архивные записи — это правило вызывающего, handbook его не навязывает.

Алгоритм

  1. Контекст и доступ. Вызывает m2m service-account из allowlist internal-области; policy InternalServiceOnly. Tenant и user-permissions не применяются.
  2. Проверка входа. Суммарное число id по всем спискам после дедупликации — 1..500; иначе VALIDATION_FAILED (400).
  3. Выборка. Записи каждого типа по спискам id — включая архивные (INV-HND-5). Несуществующие id не являются ошибкой и никак не отмечаются — соответствующего элемента просто нет в ответе (вызывающий сравнивает запрошенное с полученным).
  4. Результат. 200 + ResolveReferencesResponse: пять коллекций (пустые — для типов, по которым id не запрашивались или ничего не найдено). Порядок элементов не гарантируется — сопоставление по id.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401нет/невалиден m2m service tokenбез телаобновить токен
— (authz)403токен вне allowlist internal-областибез телаno retry
VALIDATION_FAILED400суммарно 0 id либо > 500 id«От 1 до 500 идентификаторов суммарно»no retry (разбить на батчи)
Частичный результат — не ошибка

200 с найденным подмножеством. HANDBOOK_ENTRY_NOT_FOUND в batch-resolve не используется (семантика отсутствия — пропуск элемента, INV-HND-5).

Acceptance Criteria

  • Смешанный батч. Given существующие CountryIds=[1], CityIds=[10, 999999] (второй город не существует); When resolve; Then 200: Countries содержит страну 1, Cities — только город 10; элемента для 999999 нет; Regions/Currencies/Languages — пустые массивы.
  • Архивная запись резолвится. Given город заархивирован администратором; When resolve по его id; Then 200, город присутствует в Cities с IsArchived = true и актуальными Name/CountryId/RegionId.
  • Гео-цепочка. Given город с регионом; When resolve; Then элемент города несёт CountryId и RegionId — потребитель может сверить согласованность без дополнительных вызовов.
  • Пустой запрос. Given все списки отсутствуют или пусты; When resolve; Then 400 VALIDATION_FAILED.
  • Превышение лимита. Given суммарно 501 id; When resolve; Then 400 VALIDATION_FAILED.
  • Дубликаты. Given CurrencyIds=[1,1,1]; When resolve; Then 200, валюта 1 присутствует в Currencies ровно один раз (дубликаты схлопнуты, не ошибка).
  • Чужой сервис. Given m2m-токен сервиса вне allowlist; When resolve; Then 403.
  • Повтор безопасен. Given потребитель ретраит вызов; When resolve дважды; Then оба ответа идентичны (read-only, состояний не меняет).

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

Breaking changes

Расширения resolve-моделей — только additive (новые optional-поля), синхронно с правилами snapshots.md; удаление/переименование/смена типа — breaking сразу для всех потребителей allowlist (согласованный rollout). Неизвестные поля ответа потребитель игнорирует. Семантика «неизвестный id отсутствует в ответе» — контракт: добавление признаков «почему не найден» — additive-расширение, замена пропуска на ошибку — breaking.

  • SLO: p99 < 100 мс при батче 500 id (операция стоит на write-path потребителей); PostgreSQL, point-чтения по PK (ADR-0063 п.7). При недоступности handbook потребитель решает сам: блокировать операцию (строгая валидация crm/profile) или деградировать (мягкая валидация, локальная реплика chessboard).
  • Logs: handbook-resolve requested (потребитель из токена, счётчики запрошено/найдено по типам — без перечисления id); metrics: handbook_resolve_total{consumer}, handbook_resolve_ids_total{kind,outcome=found|missing}, latency p99; traces — OTel (traceparent); dashboard handbook-overview; alert — всплеск outcome=missing (рассинхрон данных потребителя или отставание синка).

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

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

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