DarkRiDDeR13 мин

Почему telemetry не равна списку пользователей

РефакторингAPI

Перед удалением API часто появляется уверенная фраза: «в telemetry, то есть в данных наблюдения, за две недели не видно вызовов». Она звучит как ответ на вопрос о последнем consumer, но отвечает только на вопрос о выбранном наборе наблюдений. Симптом — одно число или пустой график превращают в список пользователей. Цена ошибки — удалить путь для client, который не попал в окно, идёт по другой authorization boundary или не присылает нужный signal, а затем спорить, была ли это «неожиданная» зависимость.

Другой риск возникает раньше. Команда ставит deprecated в OpenAPI или отправляет warning, но называет это миграцией. Declaration, runtime signal и removal — разные состояния. Если смешать их, можно начать возврат только после поломки: replacement не описан, owner не назначен, Sunset date трактуется как жёсткая гарантия, а неизвестный consumer исчезает из таблицы. Практичнее держать отдельные evidence types и timeline с boundary для каждого шага.

Симптом → причина → проверка → действие

  1. Симптом. Нулевой график или один access report объявлен доказательством, что у API больше нет пользователей.
  2. Причина. Source usage, declared dependency, exposure or traffic, authorization и unknown consumer измеряют разные поверхности, но их сложили в один статус.
  3. Проверка. Для каждой строки map укажите evidence type, scope, period, owner и то, чего этот тип не может доказать.
  4. Действие. Unknown оставьте отдельным состоянием. Warning, deprecation, sunset и removal проводите по timeline, где у каждой boundary есть entry condition и stop condition.

Пять разных источников сведений

Source usage отвечает на локальный вопрос: известен ли вызов в разрешённой кодовой области. Это полезный сигнал для named service, но не доказательство всех deployed clients. Declared dependency отвечает на другой вопрос: кто явно объявил SDK, schema или API contract. Такая запись может пережить migration и не говорить о runtime execution. Эти два слоя помогают найти owner, но не позволяют объявить population полной.

Exposure or traffic отвечает на вопрос об observed requests в конкретном инструменте, периоде и маршруте. Здесь особенно опасно слово «пользователь». Request не всегда равен человеку, а отсутствие request в окне не равно отсутствию caller. Authorization описывает credential class, permission или gateway policy: она может показать, какие классы способны использовать resource, но не гарантирует, что каждый класс реально делает вызов. Unknown consumer остаётся, когда scope нельзя честно замкнуть.

Evidence types для lifecycle API
ТипПолезный вопросТипичная ложная подменаЧто записать рядом
Source usageесть ли known call в согласованной code scope?не нашли call → никто не использует APIrepository or module scope, revision, owner, blind zones
Declared dependencyкто объявил SDK, schema или contract?зависимость → текущий runtime callartifact, version, owner, migration target
Exposure / trafficчто observed в заданном route and period?нет samples → нет usersinstrument, interval, sampling, auth and cache boundary
Authorizationкакой credential class может обратиться?credential exists → active consumerpolicy scope, owner, exceptions, review date
Unknown consumerкакая часть population не доказана?unknown → zeroreason, risk owner, next authorized check or decision
Timeline deprecation разделяет четыре состояния: documentation warning, declared deprecation, sunset boundary и отдельный removal gate. Под каждой стадией отмечены требуемые поля: replacement, owner, evidence scope, stop condition и restore boundary.
Временная шкала показывает порядок состояний, а не календарную политику. Даты и пункты в статье — fixed synthetic values; схема не читает telemetry и не указывает реальный срок отключения.

Warning, deprecation, sunset и removal не являются одним событием

Warning — это discoverable documentation: consumer может увидеть replacement и условия перехода, но сам resource продолжает работать. OpenAPI 3.1.0 позволяет объявить operation устаревшей в contract. IETF draft-09 описывает Deprecation header как runtime signal и допускает Link на deprecation documentation. Ничто из этого не выключает endpoint. Это правильно: notice должен уменьшать появление новых зависимостей, не меняя semantics незаметно.

Sunset — следующий уровень риска. RFC 8594 описывает timestamp, после которого URI ожидаемо станет unresponsive. Документ специально отделяет стадию «не preferred» от decommission и называет Sunset hint. Поэтому нельзя использовать timestamp как доказательство того, что все callers успели перейти. Он задаёт boundary для plan, например: после неё compatibility может закончиться, но до actual removal всё равно нужен check, owner и безопасный stop.

Lifecycle timeline и условия перехода
СостояниеЧто становится видимымЧто ещё запрещено утверждатьBoundary для следующего шага
Warningreplacement, owner, migration guide и scope noticeчто caller увидел notice или начал migrationdeclaration and documentation reviewed
DeprecatedOpenAPI flag or Deprecation signal для resourceчто response semantics уже изменились или consumer ушлиnamed consumer map and evidence boundary recorded
Sunset boundaryпланируемая дата возможной недоступности URIчто endpoint обязательно выключится именно в момент timestamphuman review of residual risk and restore boundary
Removal gateотдельный decision о changeчто hidden consumer невозможенauthorized evidence, stop condition, safe rollback or restore plan

Ограниченная воспроизводимая модель

Ниже нет HTTP call, file read, code search, customer record, CI or production. Функции принимают только один case id и возвращают fixed objects из этого модуля. Это намеренное ограничение. Оно позволяет проверить строгие input and report contracts: extra key, sparse array, forged decision и cyclic JSON не превращаются в «зелёный» план. Но fixture не доказывает ни один факт о реальном API.

import {
  createFixedSyntheticDeprecationInput,
  inspectSyntheticDeprecation,
  planSyntheticDeprecationReview,
  restoreSyntheticDeprecationReview,
  runDeprecationFixture,
} from './upgrade-2024-11.mjs';

const report = inspectSyntheticDeprecation(
  createFixedSyntheticDeprecationInput('fixed-migrated-consumer-v1'),
);
const draft = planSyntheticDeprecationReview(report);
const restored = restoreSyntheticDeprecationReview(draft);

if (!Object.values(runDeprecationFixture().assertions).every(Boolean)) {
  throw new Error('fixed synthetic fixture failed');
}

console.log({ decision: report.decision.code, restored: restored.restored });

// Fixed objects in memory only.
// No files, Git, network, CI, clock, production, telemetry, customer list or real API is accessed.
// restored means discard of the teaching draft, not rollback of a deployed endpoint.

В модели есть три case. fixed-active-consumer-v1 блокирует removal, потому что одна fixed row active и другая unknown. fixed-unknown-consumer-v1 показывает более неприятную ветку: visible row migrated, но authorization scope remains unknown. fixed-migrated-consumer-v1 разрешает только human review proposal — не deletion — потому что named rows migrated, а undiscovered client не доказан отсутствующим.

Почему строгая форма важнее красивого summary

Если report можно дополнить произвольным полем traffic: 0, следующий reviewer может принять внешний факт без scope and method. Если array consumer map sparse, «пустое место» выглядит как строка, но у него нет owner and evidence. Если forged decision заменяет block-removal на remove-now, description перестаёт соответствовать fixed case. Поэтому fixture требует exact keys, dense arrays, canonical JSON и безопасно отвергает cycle.

Это не security control для реальных data. Это дисциплина учебного примера: model should not create its own fake evidence. В реальной работе содержимое report нужно получать только в разрешённой процедуре и хранить с method, period, scope and owner. Даже тогда его следует читать как evidence for a question, not universal consumer list.

Как построить timeline без ложной автоматизации

  1. Назовите resource scope. Method, URI template, operationId, version and replacement должны описывать один предмет, а не «весь API».
  2. Опубликуйте warning. Добавьте migration guide, owner and communication path. Это уменьшает новые dependencies, но не подтверждает чтение notice.
  3. Объявите deprecation. Сверьте OpenAPI contract и, если выбран HTTP signal, его resource scope. Не меняйте functional behavior под видом notice.
  4. Поставьте sunset boundary. Назовите дату как planned availability boundary и document, что client must not treat it as hard promise.
  5. Соберите evidence by type. Source, dependency, exposure, authorization and unknown rows не склеивайте. Для каждой запишите blind zone.
  6. Откройте removal review. До отдельного change назовите active blockers, residual unknown, stop condition and restore boundary.

Stop condition и restore boundary

Stop condition должен быть короче, чем план удаления. Пример: «появилась active row в разрешённой проверке» или «owner replacement contract не может подтвердить compatibility». В этот момент proposal останавливают, а не «дочищают» ещё два слоя, чтобы сохранить дату. Restore boundary отвечает на следующий вопрос: куда команда возвращается, пока реальное удаление не началось? Для API это обычно last documented compatible contract, а не магическое «вернуть всё назад».

В synthetic model restoreSyntheticDeprecationReview умеет только выбросить canonical in-memory draft. Это специально слабая операция. Она не открывает route, не трогает headers and configuration и не возвращает traffic. Настоящий rollback должен описывать authorization, schema and data compatibility отдельно. If a new client contract already writes irreversible state, a timestamp cannot be its restore plan.

Ограничения и следующий проверяемый шаг

Эта статья не предлагает метод собирать customer list и не описывает реальную telemetry. В ней нет access-log query, real request count, credential, incident, metric или active account. Fixed labels созданы для проверки формата, а не для оценки нагрузки. SemVer и OpenAPI задают versioning and contract vocabulary, а RFC 8594 и IETF draft-09 задают lifecycle signals; ни один источник не делает один график доказательством отсутствия всех consumers.

Следующий шаг: возьмите один endpoint и создайте пять строк evidence types из таблицы. В каждой добавьте самый опасный blind zone. Если для строки exposure нельзя назвать tool and period, она остаётся unknown. Ожидаемый результат — timeline без фальшивого зелёного статуса: команда знает, что именно объявлено, что observed, что не доказано и какой факт остановит removal review.

Историческая граница ноября 2024

Источники зафиксированы до ноября 2024: RFC 8594 (May 2019), IETF draft-ietf-httpapi-deprecation-header-09 (September 2024), OpenAPI Specification v3.1.0 и Semantic Versioning 2.0.0. Draft-09 в этот момент не был RFC. Его signal и RFC Sunset — information about lifecycle, не telemetry contract. Все labels, states, dates, actions and outcomes в fixture — fixed synthetic in-memory records; script не обращается к network, files, Git, CI, production, telemetry, authorization или customer data.

Проверяемые источники

  • RFC 8594: The Sunset HTTP Header Field, May 2019 — Sunset сообщает, что конкретный URI, вероятно, станет недоступен в указанную дату; это hint, а не гарантия доступности или доказательство миграции. Граница: RFC различает стадию «не рекомендуем» и decommission: Sunset относится ко второй. Он не перечисляет клиентов, не задаёт срок уведомления и не обещает ответ после даты.
  • IETF draft-ietf-httpapi-deprecation-header-09, 27.09.2024 — Deprecation header сообщает, что ресурс уже устарел или устареет; Link с relation deprecation может вести к документации и migration guide. Граница: На ноябрь 2024 это IETF Internet-Draft, не опубликованный RFC. Сам header не меняет поведение ресурса и не является списком его пользователей.
  • OpenAPI Specification v3.1.0: Operation Object — Поле deprecated в Operation Object объявляет операцию устаревшей; consumers SHOULD refrain from usage, default false. Граница: Спецификация описывает декларацию в контракте. Она не подтверждает, что generated client обновлён, вызовов нет или replacement совместим с каждым потребителем.
  • Semantic Versioning 2.0.0 — SemVer требует объявить public API; при пометке public API как deprecated увеличивается minor version, а при несовместимом изменении public API — major version. Граница: SemVer — схема версий для объявленного API, не protocol удаления. Она не даёт consumer map, calendar policy, telemetry model или authority удалить endpoint.