Files
progcode/editorial/agent-rewrites/114.json
T

8 lines
22 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 114,
"slug": "editorial-2024-11-practice-deprecation",
"title": "Депрекация API без аварии: карта потребителей и gate удаления",
"excerpt": "Дата Sunset и пустой график вызовов не доказывают, что endpoint можно удалить. Показываю, как ограничить scope, собрать карту потребителей и остановить опасное удаление.",
"contentHtml": "<p>В задаче на удаление API обычно есть убедительная дата: после 3 февраля старый путь должен исчезнуть. В день релиза график вызовов пуст, в OpenAPI стоит <code>deprecated: true</code>, а владельца старого клиента никто не знает. Обработчик удаляют. Через час внешний интегратор получает 404, а команда не может ответить, кто предупредил потребителя и где описан переход.</p>\n<p>Причина не в том, что календарь составили плохо. Дата задаёт план, но не доказывает отсутствие потребителей. Пустой график описывает только выбранный маршрут, период, прокси, credentials и качество телеметрии. Пометка в схеме сообщает о жизненном цикле операции, но не обновляет SDK и не доставляет уведомление. Без карты зависимостей удаление превращается в догадку.</p>\n<p><strong>Рабочее правило:</strong> сначала докажите границы проверки, затем принимайте решение об удалении одной операции. Результат проверки может быть <code>observed</code>, <code>not observed in scope</code> или <code>unknown</code>. Последние два результата нельзя склеивать с фразой «никто не использует». При активном или неизвестном потребителе автоматический removal gate закрыт.</p>\n<h2>Что именно мы собираемся удалить</h2>\n<p>Фраза «убираем v1» слишком широка для безопасного изменения. Она может означать один метод, несколько ресурсов, SDK-функцию, webhook или всю версию API. Начните с одной операции и запишите её как контракт: HTTP-метод, URI-шаблон, <code>operationId</code>, форму запроса, ответы, коды ошибок и границу авторизации.</p>\n<p>Такой scope нужен не для бюрократии. Владелец сервиса может подтвердить удаление <code>POST /v1/posting</code>, но не всего <code>/v1</code>. Новый URL может принимать тот же JSON, но иначе обрабатывать повторную доставку, идемпотентность, пагинацию или права. Поэтому рядом с replacement фиксируйте семантические отличия. Совпадение URL и формата ещё не означает совместимость.</p>\n<table><caption>Минимальный контракт removal proposal</caption><thead><tr><th>Поле</th><th>Что записать</th><th>Почему это ограничивает риск</th></tr></thead><tbody><tr><td>Operation</td><td><code>POST /v1/posting</code> и <code>operationId</code></td><td>Не даёт распространить решение на соседние методы</td></tr><tr><td>Replacement</td><td>Новый метод, схема, ошибки и правила повторов</td><td>Показывает, что переход — не простая замена строки</td></tr><tr><td>Population</td><td>Известные, внешние и неизвестные классы клиентов</td><td>Не маскирует неполноту инвентаризации</td></tr><tr><td>Evidence boundary</td><td>Инструмент, окно, маршрут, credentials, sampling и пропуски</td><td>Отделяет наблюдение от доказательства полноты</td></tr><tr><td>Restore boundary</td><td>Что возвращается и кто проверяет восстановление</td><td>Превращает rollback из обещания в проверяемое условие</td></tr></tbody></table>\n<h2>Почему одного сигнала недостаточно</h2>\n<p>Карта потребителей должна разделять классы сведений. Поиск исходников показывает вызовы в просмотренной области. Manifest показывает объявленную зависимость. Access log показывает запросы, дошедшие до конкретного слоя. Сведения от владельца подтверждают намерение команды, но не заменяют runtime-проверку. У каждого сигнала свой blind spot, поэтому итоговая строка хранит и результат, и границу.</p>\n<table><caption>Как читать отрицательный результат</caption><thead><tr><th>Результат</th><th>Что он действительно означает</th><th>Чего он не доказывает</th><th>Следующее действие</th></tr></thead><tbody><tr><td><code>source: found</code></td><td>В просмотренном репозитории найден вызов</td><td>Что это единственный потребитель</td><td>Назначить владельца и запланировать миграцию</td></tr><tr><td><code>traffic: zero</code></td><td>За окном не было видимых запросов</td><td>Что нет batch, другого gateway или редкого клиента</td><td>Расширить окно и сверить маршрут и credentials</td></tr><tr><td><code>declared: absent</code></td><td>Зависимость не записана в проверенном manifest</td><td>Что внешний binary или сгенерированный SDK отсутствует</td><td>Проверить inventory и канал объявления</td></tr><tr><td><code>scope: unknown</code></td><td>Полнота области не доказана</td><td>Что endpoint безопасно удалять</td><td>Сохранить совместимость или принять риск отдельно</td></tr></tbody></table>\n<figure><img src='/assets/editorial/2024/deprecation-2024-consumer-map.svg' alt='Схема удаления API: операция связана с картой потребителей, владельцами, replacement и границами доказательств; active или unknown consumer ведёт к остановке удаления'><figcaption>Карта показывает порядок решения: сначала scope и evidence, затем миграция; неизвестная область закрывает автоматическое удаление. Имена на схеме условные.</figcaption></figure>\n<p>Например, отсутствие записи в репозитории не видит закрытый исходный код, сгенерированный клиент или старый бинарный клиент. Нулевой access log не видит запрос, который прошёл через другой hostname, не попал в выбранный gateway или пришёл раз в квартал. Даже полный на вид отчёт относится к своему окну и credential class. Именно поэтому строка <code>unknown consumer</code> полезнее пустой ячейки: она сохраняет незамкнутую область в решении.</p>\n<h2>Депрекация не равна отключению</h2>\n<p>В OpenAPI 3.1 поле <code>deprecated</code> у Operation Object объявляет операцию устаревшей и рекомендует потребителям прекратить её использование. Это описание контракта. Оно не ищет клиентов, не выпускает новую версию SDK и не меняет ответ сервера.</p>\n<p>Runtime-уведомление решает другую задачу. Заголовок <code>Deprecation</code> из RFC 9745 сообщает клиенту дату депрекации; его значение — structured date, например <code>Deprecation: @1688169599</code>. Тот же RFC подчёркивает: сам факт депрекации не меняет поведение ресурса. Для документации можно добавить <code>Link</code> с отношением <code>deprecation</code>, где указаны replacement и миграционная инструкция.</p>\n<p><code>Sunset</code> из RFC 8594 сообщает, что ресурс ожидается недоступным после указанного HTTP-времени. Это подсказка для клиента, а не гарантия того, что до даты всё будет работать, а после неё обязательно появится конкретный код. В RFC 9745 также зафиксировано, что Sunset не должен быть раньше даты Deprecation. Проверяйте эти заголовки на фактическом ответе и не выдавайте их за доказательство миграции.</p>\n<pre><code>BASE_URL=https://api.example.test; curl --fail-with-body -sS -D response.headers -o response.body \"$BASE_URL/v1/posting\"; awk 'BEGIN{IGNORECASE=1} /^deprecation:|^sunset:|^link:/{print}' response.headers</code></pre>\n<p>Команда предназначена для собственного стенда: замените <code>api.example.test</code> адресом среды, где разрешена такая проверка, и не помещайте токены в shell history или публикацию. Если endpoint требует авторизацию, задайте её способом, принятым в проекте, и отдельно запишите, какой класс credentials был покрыт. Ответ одного proxy не подтверждает поведение другого слоя.</p>\n<h2>Как собрать карту потребителей</h2>\n<p>Минимальная запись содержит consumer, класс evidence, owner, replacement, migration path, срок, канал уведомления и evidence boundary. Для известного клиента нужна не только строка «migrated», а ссылка на проверку: версия SDK, тест совместимости, успешный запрос или подтверждённый rollout. Статус в таблице — это утверждение, его источник и область должны быть видны рядом.</p>\n<pre><code>consumer,kind,owner,replacement,status,evidence_boundary; checkout-web,source-and-traffic,team-checkout,POST /v2/posting,migrating,repo=checkout; partner-batch,traffic-only,partner-team,POST /v2/posting,unknown,logs=gw-a; unknown-external,unknown,api-owner,none,block,public-route</code></pre>\n<p>Это CSV-образец, а не список реальных клиентов. В рабочем процессе его можно хранить в системе, где есть история изменений и доступ владельцев. Не записывайте секреты, токены и персональные данные: для связи достаточно идентификатора системы, команды и ссылки на разрешённое доказательство.</p>\n<p>Отдельно проверяйте четыре направления: код и сгенерированные клиенты; реестр SDK и зависимостей; сетевые логи на ingress и gateway; коммуникацию с внешними владельцами. Дублируйте результат только там, где сигналы независимы. Три отчёта из одного прокси не превращаются в три доказательства.</p>\n<h2>Removal gate должен уметь остановить изменение</h2>\n<p>Хороший gate формулируется отрицательными условиями. Удаление запрещено, если найден активный consumer; если неизвестная область не ограничена и остаточный риск не принят; если replacement меняет значимую семантику без плана; если notice не связан с affected scope; если не описано восстановление. Ранняя дата не отменяет ни одного из этих условий.</p>\n<p>Разделяйте два решения. Первое — можно ли объявить операцию устаревшей и начать миграцию. Второе — можно ли менять runtime так, чтобы старый путь перестал отвечать. Между ними должны быть окно совместимости, подтверждённые владельцы и отдельное human review. Не объединяйте удаление route, изменение прав, чистку документации и удаление SDK в один непроверяемый коммит.</p>\n<table><caption>Вердикты перед удалением</caption><thead><tr><th>Вердикт</th><th>Условие</th><th>Разрешённое действие</th></tr></thead><tbody><tr><td><code>block: active</code></td><td>Есть запросы или подтверждённая зависимость</td><td>Остановить removal, назначить миграцию</td></tr><tr><td><code>block: unknown</code></td><td>Область проверки не замкнута</td><td>Расширить наблюдение или продлить совместимость</td></tr><tr><td><code>allow: review</code></td><td>Scope, replacement, owners, notice и restore boundary описаны</td><td>Открыть отдельный review; не удалять автоматически</td></tr><tr><td><code>allow: change</code></td><td>Политика проекта допускает изменение после review</td><td>Применить узкий change и проверить соседние операции</td></tr></tbody></table>\n<h2>Воспроизводимая последовательность</h2>\n<ol><li><strong>Зафиксируйте scope.</strong> Назовите одну operation, метод, URI, operationId, схемы ответа и authentication boundary.</li><li><strong>Опишите replacement.</strong> Сравните семантику, ошибки, идемпотентность, повторы, лимиты и права, а не только URL.</li><li><strong>Соберите карту.</strong> Проверьте source usage, manifests, SDK inventory, gateway и ingress. Добавьте отдельную строку unknown, если полнота не доказана.</li><li><strong>Разметьте evidence.</strong> Для каждого результата сохраните инструмент, окно, маршрут, credentials, sampling и blind spots.</li><li><strong>Назначьте владельцев.</strong> У каждой известной зависимости должны быть owner, migration path, версия replacement и способ подтвердить переход.</li><li><strong>Опубликуйте notice.</strong> Свяжите OpenAPI, Deprecation, Sunset, Link-документацию и миграционный план с тем же scope.</li><li><strong>Проверьте stop conditions.</strong> Active, unknown без принятого риска, несовместимый replacement и отсутствующий restore boundary закрывают gate.</li><li><strong>Откройте отдельный review.</strong> В решении укажите остаточный риск, затронутую operation и критерии отказа. Только после допуска создавайте runtime change.</li><li><strong>Сверьте результат.</strong> Повторите проверку после изменения: старый путь изменился ожидаемо, replacement доступен, а соседние методы и правила авторизации не затронуты.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Эта схема не делает неизвестных клиентов видимыми автоматически. Она не заменяет договор с внешним партнёром, анализ закрытого кода, юридические сроки уведомления, требования к доступности или политику change management. Конкретное окно наблюдения и достаточная полнота зависят от трафика: для ежедневного запроса 30 дней может быть полезным сигналом, для квартального batch — нет.</p>\n<p>Заголовки тоже имеют границы. Клиент может их не читать, промежуточный кеш может изменить наблюдаемую картину, а <code>Deprecation</code> и <code>Sunset</code> не доказывают, что владелец клиента получил notice. RFC 8594 не определяет, будет ли после Sunset 4xx, 3xx или другой отказ. Поэтому не стройте единственную защиту на runtime-заголовке и не обещайте точный код ответа, если он не закреплён вашим контрактом.</p>\n<p>Если потребитель активен, безопасное действие — остановить удаление и дать ему совместимый путь. Если потребитель неизвестен, безопасное действие — расширить scope или сохранить endpoint. Residual risk можно принять только тем владельцем и в том процессе, которые отвечают за последствия; запись «вроде никто не использует» таким решением не является.</p>\n<h2>Критерий готовности</h2>\n<p>Операция готова к removal review, когда её scope однозначен, replacement проверен по семантике, известные потребители имеют владельцев и миграционные доказательства, неизвестная область либо закрыта, либо явно принята, а notice и restore boundary доступны. Решение должно быть воспроизводимым: другой инженер может повторить запрос, понять границу логов, найти источник строки карты и назвать условие остановки.</p>\n<p>Итогом не обязательно будет удаление. Иногда лучший результат — продлить совместимость, добавить телеметрию или сузить контракт. Дата в календаре становится полезной только после того, как рядом появились scope, доказательство, владелец и понятный следующий шаг.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://spec.openapis.org/oas/v3.1.0.html#operation-object\" target=\"_blank\" rel=\"noopener\">OpenAPI Specification 3.1.0: Operation Object</a> — поле <code>deprecated</code> объявляет операцию устаревшей и не описывает список потребителей.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc9745.html\" target=\"_blank\" rel=\"noopener\">RFC 9745: The Deprecation HTTP Response Header Field</a> — формат заголовка, отношение <code>deprecation</code>, связь с документацией и границы runtime-сигнала. RFC опубликован в 2025 году, поэтому для процесса, зафиксированного раньше, отдельно проверьте поддерживаемый набор заголовков.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8594.html\" target=\"_blank\" rel=\"noopener\">RFC 8594: The Sunset HTTP Header Field</a> — HTTP-date, ожидаемая недоступность ресурса и ограничение Sunset как подсказки, а не гарантии конкретного поведения.</li></ul>"
}