Files
progcode/editorial/agent-rewrites/112.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 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": 112,
"slug": "editorial-2024-11-field-deprecation",
"title": "Как удалить устаревший API и не сломать последнего клиента",
"excerpt": "Депрекация не доказывает, что endpoint больше не используют. Разбираем removal gate: как отделить активного клиента от неизвестной зоны, выбрать проверку, остановить удаление и определить готовность изменения.",
"contentHtml": "<p>После миграции старого endpoint команда видит зелёные тесты, пустой список известных клиентов и открывает удаление. В следующем релизе один интегратор получает 404 или 410. Его не нашли, потому что поиск прошёл только по репозиторию, а клиент жил в другом аккаунте, в старой версии SDK или за пределами выбранных логов. Цена ошибки — не только один сбой. Команда теряет совместимость, получает срочный откат и уже не может точно сказать, какую область проверила.</p>\n<p>Проблема начинается с неверного вопроса: «кто последний consumer?». Полный список потребителей часто недостижим. Рабочий вопрос уже: «какие условия допускают удаление этого ресурса, что осталось неизвестным и какое наблюдение остановит change?». Это removal gate — отдельная проверка перед удалением. Она не обещает отсутствие скрытых клиентов. Она делает риск ограниченным, видимым и управляемым.</p>\n<h2>Что именно устаревает</h2>\n<p>Сначала зафиксируйте один ресурс. Это может быть <code>GET /v1/orders/{id}</code>, операция с конкретным <code>operationId</code> или поле ответа в версии контракта. Не называйте предметом проверки «старый API» целиком. У разных маршрутов будут разные владельцы, клиенты и сроки.</p>\n<p>Депрекация меняет статус ресурса, но не должна незаметно менять его поведение. В OpenAPI поле <code>deprecated: true</code> сообщает о статусе операции. HTTP-заголовок <code>Deprecation</code> сообщает тот же сигнал во время запроса. Ссылка через <code>Link</code> может вести к описанию причины и замены. Ни один из этих сигналов не доказывает, что клиент прочитал уведомление и перешёл на новый маршрут.</p>\n<p><code>Sunset</code> тоже не является доказательством. Он обозначает ожидаемую границу, после которой ресурс может стать недоступным. Это дата для миграционного плана, а не подтверждение, что все callers уже ушли. Между уведомлением и удалением нужен отдельный decision.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Все найденные клиенты migrated</td><td>Список известных строк приняли за полную population</td><td>Назвать scope источника, период и blind zone</td><td>Оставить unknown и заблокировать автоматическое удаление</td></tr><tr><td>Трафик равен нулю</td><td>Проверка не видит нужный регион, credential или кэш</td><td>Сверить охват telemetry с ресурсом и клиентами</td><td>Расширить разрешённую проверку или сохранить совместимость</td></tr><tr><td>Есть <code>Deprecation</code> и дата <code>Sunset</code></td><td>Сигнал перепутали с фактом миграции</td><td>Проверить replacement, доставку notice и статус каждого клиента</td><td>Продолжить миграцию; removal gate не закрывать</td></tr><tr><td>Нашёлся active consumer</td><td>Владелец начал change до согласования последнего клиента</td><td>Проверить owner, контракт и путь перехода</td><td>Остановить удаление и вернуть задачу на миграцию</td></tr><tr><td>Неясно, как откатить change</td><td>Restore boundary не описали до удаления</td><td>Назвать последний совместимый контракт и stop condition</td><td>Не начинать removal change</td></tr></tbody></table>\n<h2>Механизм removal gate</h2>\n<p>Разделите результат на три состояния. <strong>Active</strong> означает, что проверка нашла действующий вызов или зависимость. <strong>Unknown</strong> означает, что область не наблюдается или её нельзя проверить в разрешённом scope. <strong>Migrated</strong> означает, что названная зависимость перешла на replacement. Эти слова описывают разные факты. Нельзя превратить unknown в migrated только потому, что известные строки уже закрыты.</p>\n<p>Active сразу блокирует удаление. У него должен быть владелец, способ связаться с ним и новый контракт. Unknown тоже блокирует автоматическое удаление, но по другой причине: неизвестность не равна нулевой активности. Для неё нужен владелец остаточного риска и конкретное решение — расширить проверку, продлить поддержку или принять ограниченный риск на human review. Migrated допускает подготовку предложения, но не означает, что маршрут можно удалить без отдельного change.</p>\n<p>Каждая строка evidence должна отвечать на пять вопросов: какой ресурс проверяли, каким инструментом, за какой период, в какой области и чего инструмент не видит. Запись «usage = 0» без этих полей слаба. Она выглядит точной, но не объясняет, что именно измерено.</p>\n<figure><img src=\"/assets/editorial/2024/deprecation-2024-removal-gate.svg\" alt=\"Схема removal gate для активного, неизвестного и мигрированного клиента\"><figcaption>Removal gate разделяет active, unknown и migrated. Схема учебная: она показывает порядок решения, но не обнаруживает реальных клиентов.</figcaption></figure>\n<h2>Учебный пример контракта</h2>\n<p>Ниже приведён ограниченный учебный пример. Он не читает access log, код, сеть, CI или production и не возвращает реальные данные. Его задача — показать форму записи, в которой неизвестная зона остаётся явной.</p>\n<pre><code>const review = {\n resource: {\n method: 'GET',\n path: '/v1/orders/{id}',\n operationId: 'getOrderV1',\n replacement: 'GET /v2/orders/{id}'\n },\n evidence: [\n {\n state: 'migrated',\n subject: 'checkout-service',\n source: 'dependency inventory',\n scope: 'repository set A, reviewed 2024-11-20',\n blindZone: 'runtime clients outside set A'\n },\n {\n state: 'unknown',\n subject: 'external integrations',\n source: 'not checked',\n scope: 'none',\n blindZone: 'all external credentials'\n }\n ],\n decision: 'block-removal',\n stopCondition: 'any active row or unresolved unknown scope',\n restoreBoundary: 'keep v1 route until approved removal change'\n};</code></pre>\n<p>В этом примере первый consumer migrated, но второй остаётся unknown. Поэтому итог — <code>block-removal</code>. Поле <code>blindZone</code> не украшает отчёт. Оно показывает, почему у команды нет права назвать результат полным. Если для unknown нельзя назвать следующую разрешённую проверку, риск нужно принять явно или сохранить старый контракт.</p>\n<h2>Порядок действий</h2>\n<ol><li><strong>Определите границу.</strong> Запишите метод, URI или поле, версию, operationId, replacement и владельца. Уберите из формулировки соседние операции.</li><li><strong>Остановите новые зависимости.</strong> Обновите документацию и контракт. Добавьте понятную ссылку на замену. Если применяете <code>Deprecation</code>, проверьте scope заголовка. Notice не должен менять semantics ответа.</li><li><strong>Назначьте срок как boundary.</strong> При необходимости объявите <code>Sunset</code> и объясните, что это ожидаемая дата возможной недоступности. Не выдавайте её за гарантию миграции.</li><li><strong>Соберите evidence по типам.</strong> Разделяйте исходный код, зависимости, runtime-наблюдение, authorization scope и неизвестные области. Для каждой строки храните инструмент, период, охват и blind zone.</li><li><strong>Разнесите клиентов по состояниям.</strong> Active блокирует. Unknown блокирует автоматическое удаление. Migrated переводит вопрос на human review, но не закрывает его сам.</li><li><strong>Сформулируйте stop condition.</strong> Например: «проверка нашла active row» или «owner replacement не подтвердил совместимость». При таком факте review прекращается.</li><li><strong>Опишите restore boundary.</strong> Назовите последний совместимый контракт, способ вернуть маршрут и ограничения отката. Если изменение уже записывает необратимые данные, возврат HTTP-маршрута не решает проблему.</li><li><strong>Проведите отдельный removal review.</strong> Удаление должно быть самостоятельным изменением с понятным владельцем, residual risk и планом проверки после релиза.</li></ol>\n<h2>Отрицательный путь важнее зелёного статуса</h2>\n<p>Хороший gate часто заканчивается отказом. Это не ошибка процесса. Если есть active row, команда получает конкретную работу по миграции. Если есть unknown, команда не маскирует пробел красивым нулём. Если replacement меняет поля, коды ошибок или порядок авторизации, старый маршрут остаётся до согласования совместимости.</p>\n<p>Опасный путь выглядит иначе: поиск вернул пусто, в отчёте написали «клиентов нет», дату sunset приняли за дедлайн, а удаление объединили с миграцией. Такой результат нельзя воспроизвести и нельзя честно откатить. Пустой результат — это только утверждение инструмента в его границах.</p>\n<h2>Ограничения</h2>\n<p>Ни один источник не даёт универсального способа доказать отсутствие всех consumers. Логи могут не охватить редкий вызов. Dependency inventory не видит динамически собранный URL. Внутренний сервис может ходить через общий gateway. Credential scope может скрывать другой tenant. Кэш и очередь могут отложить вызов за пределы выбранного периода. Поэтому removal gate должен хранить границу наблюдения, а не только вердикт.</p>\n<p>Учебная таблица и код выше не являются telemetry, списком клиентов, результатом incident analysis или production evidence. Их можно использовать как шаблон полей. Реальные значения нужно получать из разрешённых систем и проверять у владельцев этих систем. Если доступ к источнику отсутствует, состояние остаётся unknown.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Удаление готово к отдельному change только тогда, когда одновременно выполнены пять условий: ресурс и replacement однозначно определены; notice и его scope опубликованы; каждая известная зависимость имеет состояние и владельца; unknown-зона записана с методом, периодом и stop condition; restore boundary проверена на совместимом контракте. Финальный review должен ответить «да» или «нет» на каждый пункт.</p>\n<p>После удаления проверьте не только код ответа. Проверьте, что новый маршрут принимает прежние обязательные сценарии, что старый маршрут действительно недоступен в заявленной области и что ошибки не появились у клиентов, которых охватил change. Если хотя бы один критерий не проверен, удаление не закончено — оно только запланировано.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9745.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9745: The Deprecation HTTP Response Header Field</a> — назначение заголовка, его scope и отличие сигнала от изменения поведения ресурса.</li><li><a href=\"https://www.rfc-editor.org/info/rfc8594/\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 8594: The Sunset HTTP Header Field</a> — смысл ожидаемой границы недоступности и ограничения timestamp.</li><li><a href=\"https://spec.openapis.org/oas/latest.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenAPI Specification</a> — описание свойства <code>deprecated</code> у операции.</li></ul>"
}