Перед удалением API часто появляется уверенная фраза: «в telemetry, то есть в данных наблюдения, за две недели не видно вызовов». Она звучит как ответ на вопрос о последнем consumer, но отвечает только на вопрос о выбранном наборе наблюдений. Симптом — одно число или пустой график превращают в список пользователей. Цена ошибки — удалить путь для client, который не попал в окно, идёт по другой authorization boundary или не присылает нужный signal, а затем спорить, была ли это «неожиданная» зависимость.
Другой риск возникает раньше. Команда ставит deprecated в OpenAPI или отправляет warning, но называет это миграцией. Declaration, runtime signal и removal — разные состояния. Если смешать их, можно начать возврат только после поломки: replacement не описан, owner не назначен, Sunset date трактуется как жёсткая гарантия, а неизвестный consumer исчезает из таблицы. Практичнее держать отдельные evidence types и timeline с boundary для каждого шага.
Симптом → причина → проверка → действие
- Симптом. Нулевой график или один access report объявлен доказательством, что у API больше нет пользователей.
- Причина. Source usage, declared dependency, exposure or traffic, authorization и unknown consumer измеряют разные поверхности, но их сложили в один статус.
- Проверка. Для каждой строки map укажите evidence type, scope, period, owner и то, чего этот тип не может доказать.
- Действие. 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 нельзя честно замкнуть.
| Тип | Полезный вопрос | Типичная ложная подмена | Что записать рядом |
|---|---|---|---|
| Source usage | есть ли known call в согласованной code scope? | не нашли call → никто не использует API | repository or module scope, revision, owner, blind zones |
| Declared dependency | кто объявил SDK, schema или contract? | зависимость → текущий runtime call | artifact, version, owner, migration target |
| Exposure / traffic | что observed в заданном route and period? | нет samples → нет users | instrument, interval, sampling, auth and cache boundary |
| Authorization | какой credential class может обратиться? | credential exists → active consumer | policy scope, owner, exceptions, review date |
| Unknown consumer | какая часть population не доказана? | unknown → zero | reason, risk owner, next authorized check or decision |
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.
| Состояние | Что становится видимым | Что ещё запрещено утверждать | Boundary для следующего шага |
|---|---|---|---|
| Warning | replacement, owner, migration guide и scope notice | что caller увидел notice или начал migration | declaration and documentation reviewed |
| Deprecated | OpenAPI flag or Deprecation signal для resource | что response semantics уже изменились или consumer ушли | named consumer map and evidence boundary recorded |
| Sunset boundary | планируемая дата возможной недоступности URI | что endpoint обязательно выключится именно в момент timestamp | human 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 без ложной автоматизации
- Назовите resource scope. Method, URI template, operationId, version and replacement должны описывать один предмет, а не «весь API».
- Опубликуйте warning. Добавьте migration guide, owner and communication path. Это уменьшает новые dependencies, но не подтверждает чтение notice.
- Объявите deprecation. Сверьте OpenAPI contract и, если выбран HTTP signal, его resource scope. Не меняйте functional behavior под видом notice.
- Поставьте sunset boundary. Назовите дату как planned availability boundary и document, что client must not treat it as hard promise.
- Соберите evidence by type. Source, dependency, exposure, authorization and unknown rows не склеивайте. Для каждой запишите blind zone.
- Откройте 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.