Files
progcode/editorial/agent-rewrites/113.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
16 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": 113,
"slug": "editorial-2024-11-mechanism-deprecation",
"title": "Deprecation API: почему пустая telemetry не разрешает удаление",
"excerpt": "Пустой график вызовов не доказывает отсутствие потребителей API. Разбираем пять типов evidence, границы deprecation и removal gate на ограниченном примере.",
"contentHtml": "<p>Команда собирается удалить старый endpoint. За последние две недели в dashboard нет вызовов. В access log видны только запросы к новой версии. Кто-то делает вывод: потребителей больше нет.</p>\n<p>Симптом выглядит убедительно, но он описывает только выбранный источник наблюдения. В него могли не попасть редкие клиенты, другой gateway, кэш, batch-задача или credential с отдельной политикой доступа. Цена ошибки — несовместимый релиз для неизвестного caller. Ошибка проявится после удаления, когда старый контракт уже не с чем сравнивать.</p>\n<p>Тезис статьи простой: deprecation — это управляемый переход, а не доказательство отсутствия пользователей. Сначала нужно разделить типы сведений и их границы. Затем объявить замену, предупредить потребителей, определить sunset boundary и только после отдельной проверки открыть removal gate. Ни один сигнал сам по себе не превращает пустую telemetry в полный список клиентов.</p>\n<h2>Что именно нужно доказать</h2>\n<p>Начните с одной операции: method, URI template, operationId и версия контракта. Формулировка «удаляем старый API» слишком широка. Для <code>POST /v1/ledger/entries</code> вопрос звучит точнее: какие callers ещё зависят от поведения этой операции, кто отвечает за замену и какие наблюдения покрывают маршрут?</p>\n<p>Source usage показывает вызов в заданном дереве исходников. Он помогает найти известный сервис, но не видит закрытый репозиторий, скомпилированный клиент или deployed версию, которая не совпала с локальной веткой. Declared dependency показывает объявленный SDK, schema или contract. Зависимость может остаться в manifest после миграции и не доказывает runtime-вызов.</p>\n<p>Exposure or traffic показывает observed requests в конкретном инструменте, маршруте, периоде и sampling policy. Запрос не равен пользователю. Один сервис может отправить тысячу запросов, а редкий клиент — один запрос за месяц. Нулевое значение означает «в этом scope сигнал не найден», а не «caller отсутствует».</p>\n<p>Authorization показывает, какой credential class или permission способен обратиться к resource. Способность не равна активности. Если политика допускает партнёрский ключ, это ещё не доказывает, что партнёр вызывает операцию. Но если такой класс не учтён, removal имеет слепую зону.</p>\n<p>Unknown consumer — не ошибка заполнения таблицы. Это честное состояние, когда scope нельзя замкнуть. Его нужно хранить отдельно от zero и migrated. Именно unknown должен блокировать автоматическое решение об удалении.</p>\n<table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>График вызовов равен нулю</td><td>Окно или route не покрывает всех callers</td><td>Записать инструмент, период, sampling, cache и auth boundary</td><td>Оставить consumer unknown и расширить разрешённую проверку</td></tr><tr><td>В manifest осталась зависимость</td><td>Declared dependency приняли за runtime usage</td><td>Сверить версию, owner и фактический call path</td><td>Назначить migration owner, не удалять endpoint по одной записи</td></tr><tr><td>В OpenAPI стоит deprecated</td><td>Декларацию приняли за завершённую миграцию</td><td>Найти replacement и проверить его контракт</td><td>Опубликовать warning и сохранить совместимое поведение</td></tr><tr><td>Наступила дата Sunset</td><td>Дату приняли за доказательство, что все ушли</td><td>Провести removal review по отдельному scope</td><td>Остановить change при active или unknown row</td></tr></tbody></table>\n<h2>Четыре границы lifecycle</h2>\n<p><strong>Warning</strong> делает замену видимой. Документация должна назвать replacement, owner, область действия и способ задать вопрос. Warning не подтверждает, что клиент его прочитал. Поэтому он не меняет функциональное поведение endpoint.</p>\n<p><strong>Deprecation</strong> сообщает, что операция больше не является предпочтительной. В OpenAPI это поле <code>deprecated: true</code>. В HTTP можно использовать Deprecation header и ссылку на документацию, если такой сигнал поддерживают ваши клиенты. Эти механизмы помогают обнаружить новые зависимости, но не выключают ресурс и не перечисляют callers.</p>\n<p><strong>Sunset</strong> задаёт планируемую границу возможной недоступности. RFC 8594 описывает её как сигнал о том, что конкретный URI, вероятно, станет недоступен в указанное время. Это hint, а не доказательство миграции и не гарантия, что сервер исчезнет ровно в timestamp. До этой границы всё равно нужна проверка остаточного риска.</p>\n<p><strong>Removal</strong> — отдельное несовместимое изменение. Оно должно иметь узкий scope, owner, stop condition и restore boundary. Если одна строка consumer map имеет состояние <code>active</code> или <code>unknown</code>, автоматическое удаление нельзя считать безопасным. Состояние <code>migrated</code> разрешает только review: нужно проверить замену, данные и поведение, а не просто закрыть старый маршрут.</p>\n<figure><img src=\"/assets/editorial/2024/deprecation-2024-timeline.svg\" alt=\"Временная шкала deprecation: warning, deprecated, sunset и removal gate с replacement, owner, evidence scope и stop condition\" loading=\"lazy\" /><figcaption>Сигналы идут последовательно, но не заменяют друг друга. Иллюстрация показывает порядок решений; она не содержит telemetry и не задаёт реальную календарную дату.</figcaption></figure>\n<h2>Учебный пример: одна операция и три состояния</h2>\n<p>Ниже приведён синтетический пример. Имена, даты и строки не получены из production, telemetry или списка клиентов. Они нужны, чтобы показать логику решения на одном endpoint.</p>\n<pre><code>operation: POST /v1/ledger/entries\nreplacement: POST /v2/ledger/entries\nowner: ledger-team\nsunset: 2025-02-03\n\nconsumer evidence state\ncheckout-service source usage active\nmobile-sdk declared dependency migrated\npartner-gateway authorization unknown\n\nremoval: block\nreason: active and unknown consumers remain</code></pre>\n<p>Первая строка содержит наблюдаемый вызов. Она блокирует removal. Вторая подтверждает план перехода, но сама по себе не доказывает, что старый endpoint больше не вызывается. Третья показывает capability boundary: gateway может иметь доступ, однако его активность не установлена. Вердикт должен остаться <code>block</code>.</p>\n<p>Если заменить значение <code>unknown</code> на <code>zero</code>, факты не изменятся. Изменится только видимость риска. Поэтому consumer map должна хранить не только state, но и evidence scope: tool, period, route, sampling, auth boundary, owner и blind zone. Без этих полей строка не воспроизводится.</p>\n<h2>Порядок действий</h2>\n<ol><li>Зафиксируйте одну операцию и точный replacement. Не объединяйте в одну карту разные URI, версии и semantics.</li><li>Назначьте owner старого контракта и owner замены. Запишите, кто может остановить change.</li><li>Добавьте warning в документацию и migration guide. Опишите совместимость, различия ответов и путь поддержки.</li><li>Объявите deprecation в OpenAPI или согласованным HTTP-сигналом. Не меняйте поведение endpoint только из-за notice.</li><li>Соберите evidence по отдельным типам: source usage, declared dependency, exposure or traffic, authorization и unknown. Для каждой записи укажите scope и слепую зону.</li><li>Проверьте replacement на тех же входах и ошибках, которые важны для старого контракта. «Клиент обновился» недостаточно без проверки поведения.</li><li>Назначьте sunset boundary как плановую дату и заранее определите stop condition. Active row, unknown row или несовместимый replacement должны останавливать удаление.</li><li>Проведите human review removal. Решение должно содержать residual risk, restore boundary и ссылку на evidence. Только затем выполняйте отдельный change.</li></ol>\n<h2>Отрицательный путь</h2>\n<p>Иногда все named consumers мигрировали, а неизвестный класс доступа остался. Это не повод объявить карту полной. Сохраните старый контракт совместимым, сузьте следующую проверку до разрешённой auth boundary и назначьте срок пересмотра. Если нужное наблюдение нельзя получить законно или технически, риск остаётся unknown. Дата Sunset не превращает его в zero.</p>\n<p>Другой отрицательный путь — replacement меняет semantics: новые обязательные поля, другой порядок побочных эффектов или иные коды ошибок. Даже полный список callers не делает такое удаление безопасным. Сначала нужно сравнить контракты и решить, как клиент переживёт несовместимость. Если restore невозможен после записи новых данных, rollback старого route не восстановит прежнее состояние.</p>\n<h2>Ограничения</h2>\n<p>Эта схема не создаёт универсальный consumer map. Она не отменяет sampling, кэширование, batch-вызовы, закрытые сети, задержку доставки логов и различия между deployed и исходным кодом. Она также не говорит, сколько дней нужно наблюдать. Окно зависит от частоты вызовов и допустимого риска, а его границы нужно обосновать.</p>\n<p>Учебный код выше не вызывает сеть, не читает файлы и не утверждает production-результаты. Официальные спецификации описывают смысл сигналов, но не проверяют ваш API. Реальную готовность устанавливает только evidence с понятным scope и ответственным владельцем.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Removal готов к отдельному рассмотрению, если выполнены все условия: одна операция имеет названный replacement; у старого и нового контрактов есть owners; warning и deprecation опубликованы; каждая строка consumer map содержит тип evidence, scope, период и blind zone; active и unknown строки либо закрыты разрешённой проверкой, либо явно приняты владельцем как residual risk; replacement проверен на критичных входах; определены stop condition и restore boundary. Если хотя бы одно условие неизвестно, вердикт — <code>block</code>, а не «пользователей нет».</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://datatracker.ietf.org/doc/rfc8594/\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 8594: The Sunset HTTP Header Field</a> — назначение Sunset и его граница как lifecycle hint.</li><li><a href=\"https://spec.openapis.org/oas/v3.1.0.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenAPI Specification 3.1.0, Operation Object</a> — поле <code>deprecated</code> в описании операции.</li><li><a href=\"https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-deprecation-header-09\" target=\"_blank\" rel=\"noopener noreferrer\">IETF draft-ietf-httpapi-deprecation-header-09</a> — историческая для ноября 2024 спецификация HTTP-сигнала deprecation; это Internet-Draft, а не RFC.</li></ul>"
}