В контракте появляется дата: путь /v1/posting будет удалён после февраля. В день, когда дата наступает, в pull request легко написать «старый endpoint больше никому не нужен» и вырезать обработчик. Симптом понятен: календарь даёт один ясный сигнал, а список потребителей разбросан между контрактом, владельцами и документами. Цена ошибки тоже конкретна: один неизвестный клиент получает отказ, а команда не может объяснить, кому сообщили, какой replacement предложили и где безопасно остановить изменение.
Обратная ошибка не лучше: путь держат бесконечно, потому что никто не хочет отвечать за слово «последний consumer». Дата сама по себе не решает этот спор. Она полезна как boundary для планирования, но не как доказательство отсутствия вызовов. Удаление начинается не с календаря, а с карты: какой именно контракт устаревает, кто отвечает за миграцию, какое объявление видит потребитель и какой факт разрешает перейти к отдельному change на удаление.
Симптом → причина → проверка → действие
- Симптом. В задаче есть дедлайн, но нет перечня контрактов и ответственных за переход.
- Причина. Endpoint принимают за один объект, хотя вокруг него есть public contract, client integrations, authorizations, documentation и старый migration path.
- Проверка. Для каждого известного или неизвестного потребителя заполните одну строку: contract, owner, migration path, deadline, announcement и evidence boundary.
- Действие. До закрытия карты не удаляйте путь. После карты подготовьте отдельный review: что доказано, что неизвестно, какой риск остаётся и какие условия остановят удаление.
Сначала ограничить предмет удаления
Фраза «удаляем v1» слишком широкая. В ней не видно, речь о write route, одной операции в OpenAPI, SDK method, callback или полном наборе ресурсов. Начните с имени contract: HTTP method, URI template, request and response shape, authentication boundary и replacement. Если replacement меняет semantics, одной замены URL мало. Нужно записать, кто проверяет compatibility: например, сохраняется ли idempotency key, как кодируются ошибки и может ли старый client понять ответ.
OpenAPI 3.1.0 даёт полезную, но узкую декларацию: у Operation Object есть deprecated: true, и consumers должны воздерживаться от этой операции. Это не инвентаризация. Пометка в schema не рассказывает, какой generated client уже выпущен, какой сервис спрятал вызов за adapter и какая интеграция вообще не читает OpenAPI. Поэтому declaration и map — разные артефакты. Первое говорит, что контракт больше не рекомендован; второе делает migration работой с владельцами.
| Потребитель | Класс сведения | Contract и owner | Migration path | Deadline | Announcement и evidence boundary |
|---|---|---|---|---|---|
| synthetic-web-checkout | source usage | v1 write route; synthetic-checkout-owner | заменить fixed v1 call на fixed v2 contract | synthetic-2025-01-13 | synthetic note; label учебный, не code search |
| synthetic-sdk-package | declared dependency | v1 write route; synthetic-sdk-owner | сначала выпустить fixed v2 SDK surface | synthetic-2025-01-20 | synthetic release note; не package inventory |
| synthetic-unknown-integrator | unknown consumer | v1 write route; synthetic-api-owner | оставить notice discoverable и открыть scope question | synthetic-2025-02-03 | synthetic deprecation page; не customer list |
Карта не обязана притворяться полным реестром
У карты есть два честных типа строк. Известная строка связывает конкретного consumer с owner и migration path. Неизвестная строка фиксирует противоположное: область ещё не доказана. Она не портит отчёт. Она не позволяет календарю выдать желаемый ответ. Если в карте есть unknown consumer, следующий вопрос звучит не «когда удалить?», а «какой разрешённый способ ограничит неизвестность и кто примет остаточный риск?».
Это особенно важно для внешнего API. Нельзя получать список потребителей из одной телеметрической витрины и объявлять его полным: часть клиентов может не попадать в выбранное окно, использовать другой credential class, идти через proxy или вообще не иметь доступного механизма учёта. В этой статье нет ни telemetry, ни customer list. Учебная строка synthetic-unknown-integrator нужна только для того, чтобы не потерять этот класс риска в дизайне.
Deprecation record: короткий контракт перехода
Уведомление полезно, когда у него есть контекст. IETF draft deprecation header в версии 09, доступной к ноябрю 2024, описывает signal о том, что resource уже устарел или устареет, и link на документацию. RFC 8594 отдельно описывает Sunset как hint о предполагаемой недоступности. Из этого не следует, что два header автоматически мигрируют client. Они лишь делают решение discoverable. Migration guide, replacement, owner и scope должны быть отдельными полями записи.
{
"contract": "synthetic-ledger-v1-posting-path",
"owner": "synthetic-ledger-owner",
"replacement": "synthetic-ledger-v2-posting-path",
"deprecationDate": "synthetic-2024-11-04",
"sunsetBoundary": "synthetic-2025-02-03",
"announcement": "synthetic-public-deprecation-page",
"knownConsumerRule": "every row names owner and migration path",
"unknownConsumerRule": "unknown blocks automatic removal",
"restoreBoundary": "stop proposal before a real removal change"
}
Это не production configuration и не готовый header. Здесь нет реальной даты HTTP, токена, route table или client data. Запись проверяет другую вещь: нельзя провести строку к removal gate, пока не названы replacement, owner, объявление и реакция на unknown. Если public API ведётся по SemVer, versioning contract можно включить рядом: SemVer 2.0.0 требует объявить public API, а deprecated public functionality отражать minor version. Но версия пакета не заменяет communication plan для конкретного endpoint.
Как читать доказательство, не подменяя его обещанием
В consumer map колонка evidence должна называть не результат «никого нет», а способ и границу. Например, «объявленная зависимость» сообщает, что чей-то manifest или contract сказал о зависимости; «source usage» — что разрешённая проверка кода нашла конкретный call; «authorization» — что определён набор credential class. Каждая строка отвечает на свой вопрос. Ни одна не равна population всех пользователей.
| Сведение | На какой вопрос отвечает | Чего не доказывает | Безопасная формулировка |
|---|---|---|---|
| Deprecated в OpenAPI | операция больше не рекомендована в описании контракта | что consumer уже перестали её вызывать | declaration опубликована, миграция не подтверждена |
| Deprecation / Sunset signal | resource сообщил о lifecycle и boundary | что клиент получил, понял или применил notice | signal доступен в указанной response boundary |
| Declared dependency | потребитель объявил связь с API или SDK | что этот путь выполняется сейчас | зависимость требует owner и migration path |
| Unknown consumer | полнота scope не установлена | что отсутствует риск | автоматическое удаление заблокировано |
Removal gate — отдельное решение
Когда строки карты заполнены, удаление всё ещё не становится автоматическим. Нужен gate с отрицательными условиями: нет active row, unknown scope обработан отдельным решением, replacement contract читаем, announcement и дедлайн сохранены, а у change есть restore boundary. Это делает цену решения видимой. Можно ускорить удаление, сузив scope до одной operation, или отложить, если replacement ещё не может принять нужный сценарий. Нельзя компенсировать незнание более ранней датой.
Если перед реальным удалением появляется active consumer, безопасное действие — остановить proposal. Не менять одновременно documentation, route, authorization и fallback. Сначала сохранить, где обнаружен конфликт и какой compatibility boundary сохраняется. В synthetic fixture восстановление означает только discard in-memory draft: оно не возвращает endpoint, не меняет header и не обещает rollback production. В настоящем проекте restore plan должен быть отдельным, с owner, ограничением данных и проверкой после возврата.
Ограничения и следующий проверяемый шаг
Материал не выполняет source search, API call, анализ трафика, чтение access logs, customer list, Git или CI. Fixed rows не доказывают существование реальных клиентов, а synthetic deadlines не задают policy. RFC 8594 называет Sunset hint и не гарантирует, что ресурс станет недоступен ровно в дату. IETF draft-09 на дату исторической границы был draft, поэтому нельзя выдавать его за завершённый стандарт ноября 2024.
Следующий шаг: выберите одну operation, а не весь API. Создайте consumer map из шести колонок этой статьи. В первой же строке, где не удаётся назвать owner, evidence boundary или replacement, поставьте unknown. Ожидаемый результат — не преждевременное удаление, а понятное решение: какую проверку запросить, кто её владелец и почему до неё route остаётся совместимым.
Историческая граница ноября 2024
Для терминов lifecycle использованы RFC 8594 (May 2019) и IETF draft-ietf-httpapi-deprecation-header-09 (September 2024). Draft показывает deprecation signal и documentation link, но на ноябрь 2024 ещё не был RFC. OpenAPI Specification v3.1.0 подтверждает декларацию deprecated operation, а SemVer 2.0.0 — правила для заявленного public API. Все имена, строки карты, даты, outcomes и действия в этом материале — fixed synthetic in-memory model; это не реальная телеметрия, customer list, исходный код, incident или production evidence.
Проверяемые источники
- 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.