From 9086b8c80340e6481b17e7ec00d538c6ff1d9adc Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 15:38:49 +0300 Subject: [PATCH] editorial: revise articles 107-112 to 10/10 --- editorial/agent-rewrites/107.json | 6 +++--- editorial/agent-rewrites/108.json | 6 +++--- editorial/agent-rewrites/109.json | 6 +++--- editorial/agent-rewrites/110.json | 2 +- editorial/agent-rewrites/111.json | 6 +++--- editorial/agent-rewrites/112.json | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/editorial/agent-rewrites/107.json b/editorial/agent-rewrites/107.json index ea2d150..bef7e51 100644 --- a/editorial/agent-rewrites/107.json +++ b/editorial/agent-rewrites/107.json @@ -1,7 +1,7 @@ { "index": 107, "slug": "editorial-2025-01-mechanism-ai-coding-assistant", - "title": "Как проверить код, предложенный AI-помощником", - "excerpt": "AI-помощник быстро создаёт правдоподобный diff, но не знает контракт конкретного репозитория. Разбираем, как найти нарушение границы, проверить отрицательный путь и принять решение по наблюдаемым данным.", - "contentHtml": "

Самая дорогая ошибка AI-помощника часто выглядит как хороший результат. Diff компилируется. Имена понятны. Форматирование проходит. Тест happy path возвращает ожидаемую строку. После merge выясняется, что невалидный маркер превратился в пустое значение или повторный ключ успел вызвать запись перед возвратом ошибки. Команда платит не только за исправление. Она восстанавливает прежний контракт, ищет затронутых потребителей и разбирает, почему зелёная проверка пропустила проблему.

\n

Тезис простой: ответ модели — кандидат на изменение, а не доказательство корректности. Чтобы принять такой diff, нужно раздельно проверить область изменения, контракт, отрицательный путь и неизвестные границы. Если хотя бы одна граница не подтверждена, код нельзя считать готовым только потому, что он выглядит естественно.

\n

Откуда берётся правдоподобная ошибка

\n

Помощник продолжает текст по контексту задачи. Он видит имена функций, соседний код и формулировку запроса. Но репозиторий хранит больше правил, чем попало в контекст: допустимые значения, права, порядок побочных эффектов, требования потребителей, версию внешнего API и смысл пустого результата. Когда правило не выражено явно, кандидат заполняет пробел самым удобным вариантом.

\n

Так возникает подмена ответственности. Модель выбирает поведение, которое кажется локально разумным. Инженер принимает его за восстановленное требование. Тест подтверждает только тот сценарий, который в него положили. Три разных утверждения сливаются в одно слово «проверено».

\n
Что именно доказывает каждый слой проверки
СлойВопросЧто можно подтвердитьЧего это не доказывает
Ответ моделиКакой код предложен?Текст diff и его локальная гипотезаЧто поведение разрешено контрактом
КонтрактЧто разрешено и запрещено?Вход, результат и forbidden side effectЧто diff соблюдает правило
ТестЧто наблюдалось на конкретной ветке?Связь input, output и побочного эффектаЧто покрыты все потребители и среды
РевьюПочему принят этот scope?Пути файлов, владельца и решение о границеЧто runtime ведёт себя так же
НеизвестноеКаких данных нет?Честно названную непроверенную границуОтсутствие риска
\n

Учебный пример: форматирование заметки

\n

Ниже — изолированный учебный пример. Он не обращается к модели, базе данных, CI или production-сервису. В контракте есть три различающихся входа. Пустая строка означает отсутствие заметки. Невалидный маркер означает ошибку входа. Эти результаты нельзя объединять без решения владельца интерфейса.

\n
const cases = [\n  { input: 'note', expected: 'formatted-note' },\n  { input: 'blank', expected: 'absent-note' },\n  { input: 'invalid-marker', expected: 'invalid-note' },\n];\n\nfunction formatNote(input) {\n  if (input === 'blank') return 'absent-note';\n  if (input === 'invalid-marker') return 'invalid-note';\n  return `formatted-${input}`;\n}\n\n// Учебный контракт: invalid-marker нельзя превращать в ''.
\n

Правдоподобный кандидат может сократить функцию до одной условной ветки и вернуть пустую строку для всех неизвестных значений. Для видимого значения note результат останется правильным. Поэтому один позитивный тест ничего не скажет о границе. Нужны отдельные проверки для blank и invalid-marker. Если функция вызывается перед записью, нужно проверить ещё и запрет записи при ошибочном входе.

\n
expect(formatNote('note')).toBe('formatted-note');\nexpect(formatNote('blank')).toBe('absent-note');\nexpect(formatNote('invalid-marker')).toBe('invalid-note');\n\n// Отдельное требование для вызывающего кода:\n// invalid-marker не должен вызывать writeNote().
\n

Смысл примера не в конкретной функции. Он показывает способ чтения diff: для каждой изменённой ветки назовите разрешённый результат, запрещённый результат и наблюдение, которое отличит их. Объяснение модели может описать алгоритм, но не может само назначить смысл отсутствующего поля или разрешить побочный эффект.

\n
\"Матрица
Схема помогает разделить четыре вопроса до merge. Это учебная модель проверки, а не классификация production-инцидентов и не измерение качества какой-либо модели.
\n

Как читать diff по границам

\n

Сначала проверьте scope. Сравните каждый изменённый путь с задачей. Соседний полезный hunk не становится разрешённым автоматически. Если помощник добавил обработчик, конфигурацию или вызов в другом модуле, остановите проверку и получите отдельное решение владельца. Иначе локальная оптимизация расширит поверхность изменения незаметно.

\n

Затем зафиксируйте контракт до обсуждения стиля. Запишите допустимые входы, результат для каждого класса входов и побочный эффект, которого быть не должно. Важны не только возвращаемые значения. Для операции создания записи дубликат может вернуть ошибку и не сделать ни одного write-вызова. Если такой запрет не назван, зелёный тест на ошибку не доказывает безопасность ветки.

\n

После этого свяжите тест с изменённой веткой. Тест должен называть вход, ожидаемый результат и запрещённое действие. Проверка соседней ветки не покрывает новую ветку. Линтер подтверждает форму кода. Типы подтверждают часть интерфейса. Ни один из них не восстанавливает доменное правило, которое нигде не записано.

\n

Симптомы и точечные проверки

\n
Диагностическая таблица для candidate diff
СимптомПричинаПроверкаДействие
Изменён файл, которого нет в задачеScope расширился по соседнему контекстуСверить каждый path с формулировкой и владельцемОстановить diff или оформить отдельное решение
Happy path зелёный, invalid input не описанМодель выбрала default вместо контрактаДобавить таблицу классов входа и негативный тестВернуть код к владельцу контракта
Ошибка возвращается после write-вызоваРезультат проверили, side effect — нетПроверить число и аргументы write-вызововЗапретить запись до валидации
Есть тест, но он не касается changed branchТест подтверждает другую веткуСвязать branch с конкретным input и expected outputДобавить focused negative case
Ревьюер говорит «выглядит безопасно»Неизвестная граница принята за отсутствие рискаСоставить список непроверенных consumers, прав и версийСузить обещание или получить недостающее evidence
\n

Порядок действий перед принятием

\n
  1. Сформулируйте задачу в одном абзаце: какие пути можно менять и какой результат нужен.
  2. Отделите ответ помощника от решения. Сохраните candidate diff, но не называйте его исправлением.
  3. Выпишите контракт для каждой изменённой ветки: вход, разрешённый результат и запрещённый side effect.
  4. Проверьте scope по списку файлов и строк. Каждый выход за границу требует отдельного владельца и решения.
  5. Запустите focused tests для happy path, пустого значения, невалидного значения и повторной операции, если она возможна.
  6. Проверьте отрицательный путь по наблюдаемому следу: результат ошибки, количество вызовов и состояние после отказа.
  7. Отдельно перечислите неизвестное: реальные потребители, совместимость версий, права, конкурентный доступ и нагрузка.
  8. Примите diff только после того, как reviewer может показать конкретное evidence для каждой изменённой границы.
\n

Ограничения метода

\n

Такая проверка снижает риск, но не превращает код в гарантированно корректный. Focused test может пропустить редкую последовательность. Ревью может не знать о скрытом потребителе. Статический анализ не моделирует все права и состояния. Само наличие источника или пояснения модели не заменяет запусков и проверки доменного контракта.

\n

Учебный пример выше намеренно мал. Он не даёт данных о конкретном помощнике, модели, репозитории, скорости разработки или production-ошибках. Для чувствительного кода нужно дополнительно ограничить доступ к контексту, проверить секреты, просмотреть зависимости и согласовать правила хранения исходников. Если нет данных о совместимости или владельце результата, корректное действие — остановиться и назвать пробел, а не заполнить его догадкой.

\n

Проверяемый критерий готовности можно сформулировать жёстко: для каждого изменённого пути есть владелец и разрешённый scope; для каждой изменённой ветки есть contract row; для отрицательного пути зафиксированы output и forbidden side effect; тест наблюдает именно эту ветку; неизвестные перечислены отдельно. Если один пункт отсутствует, готовность не доказана. Это не означает, что изменение нельзя сделать. Это означает, что решение требует ещё одного факта.

\n

Проверяемые источники

\n" + "title": "Как принять код, предложенный AI-помощником", + "excerpt": "AI-помощник ускоряет черновик, но не принимает решение за инженера. Разбираем, как проверить область изменения, контракт, отрицательный путь и зависимости до merge.", + "contentHtml": "

Самая дорогая ошибка AI-помощника выглядит как удачный результат: diff компилируется, имена понятны, линтер зелёный, а happy path возвращает ожидаемое значение. После merge выясняется, что невалидный маркер превратился в пустое значение, новый пакет оказался вымышленным или повторный ключ успел вызвать запись до возврата ошибки. Команда платит не только за исправление. Она восстанавливает прежний контракт и ищет затронутых потребителей.

\n

Ответ модели нужно считать кандидатом на изменение, а не доказательством корректности. Инженер проверяет четыре независимые границы: что разрешено менять, какое поведение описывает контракт, что происходит на отрицательном пути и какие данные ещё неизвестны. Если одна граница не подтверждена, естественный вид кода не делает его готовым к слиянию.

\n

Что именно делает помощник

\n

AI-кодинг-помощник продолжает код или предлагает фрагмент по доступному ему контексту: текущему файлу, открытым файлам, запросу и настройкам продукта. Такой контекст может быть полезным, но он не равен архитектуре репозитория. Скрытый consumer, правило авторизации, особый смысл пустого поля или ограничение версии API могут остаться за пределами запроса.

\n

Официальная документация GitHub описывает Copilot как инструмент, который помогает писать код и тесты, но не заменяет экспертизу пользователя. Для другого помощника нельзя автоматически переносить детали о контексте, фильтрах или хранении данных: их нужно сверять с документацией поставщика и политикой своей организации.

\n
Какой вопрос закрывает каждый слой проверки
СлойВопросНаблюдаемое свидетельствоЧего он не доказывает
Область измененияКакие пути и строки разрешены?Список файлов и hunks совпадает с задачейЧто новое поведение соответствует бизнес-правилу
КонтрактЧто разрешено для каждого класса входа?Таблица input → output и запрет побочного эффектаЧто реализация соблюдает контракт
ТестЧто реально произошло на выбранном входе?Тест проверяет changed branch и состояние после ошибкиЧто проверены все consumers, версии и нагрузки
ЧеловекКто принимает остаточный риск?Reviewer и owner видят diff, ограничения и результат проверокЧто неизвестных границ не существует
ИнструментКакие автоматические свойства проверены?Сборка, lint, security- и dependency-checksЧто инструмент понял доменный смысл
\n

Контракт нужно записать до diff

\n

До первого запроса сформулируйте не «сделай функцию», а маленькую карточку решения. В ней должны быть результат, разрешённые пути, запрещённые изменения, классы входов и способ проверки. Это не гарантирует хороший ответ. Зато объяснение модели не сможет незаметно заменить отсутствующее требование правдоподобной догадкой.

\n

Контракт должен различать хотя бы нормальный, пустой и невалидный вход. Для операции записи добавьте повторную операцию и запрет записи при ошибке. Если неизвестно, означает ли пустая строка «нет значения» или «ошибка», работу нельзя продолжать как будто это одно состояние: сначала нужен владелец контракта.

\n
const reviewCard = {\n  result: 'normalize one invoice key',\n  allowedPaths: ['src/parseInvoiceKey.js', 'test/parseInvoiceKey.test.js'],\n  forbidden: ['change authorization', 'add a default', 'add a dependency'],\n  cases: [\n    { input: 'invoice-42', output: 'invoice-42' },\n    { input: '', output: 'absent' },\n    { input: '?', output: 'invalid', writes: 0 }\n  ]\n};\n\n// Любое изменение вне allowedPaths требует нового решения.\n// Значения cases — часть учебного контракта, а не правило вашего API.
\n

Последняя оговорка важна: пример не восстанавливает контракт реального приложения. В рабочем проекте значения берут из схемы, требований, существующих тестов и поведения совместимых клиентов. Если эти источники расходятся, сначала фиксируют расхождение, а не просят модель выбрать наиболее частый вариант.

\n

Как прочитать candidate diff

\n

Сначала смотрите не на объяснение помощника, а на область изменения. Лишний файл, новый пакет, изменение прав доступа, удалённый тест или новый default — повод остановиться. Малый размер diff не является доказательством низкого риска: одна строка в фильтре может изменить поведение всех пользователей.

\n

Затем прочитайте каждую изменённую ветку как условие: какой вход в неё попадает, какой результат выходит и какой побочный эффект запрещён. Отдельно проверьте код до первого write-вызова. Ошибка, возвращённая после записи, не эквивалентна ошибке без записи.

\n
git diff --name-only --diff-filter=ACMRT HEAD^ HEAD\ngit diff --check HEAD^ HEAD\ngit diff -- src/parseInvoiceKey.js test/parseInvoiceKey.test.js\nrg -n \"parseInvoiceKey|writeInvoice|authorization\" src test\nnpm test -- --runInBand
\n

Команды предполагают Git и npm-скрипт test; в проекте с pnpm, другой тестовой оболочкой или иной базовой ревизией замените только команду запуска. git diff --check ловит пробелы и конфликтные маркеры, но не проверяет семантику. rg помогает найти consumers, однако поиск по тексту не заменяет анализ динамического вызова или конфигурации.

\n
Матрица проверки AI-предложенного diff: область изменения, контракт, тест, человеческое решение и неизвестные границы.
Пять независимых вопросов перед merge. Схема показывает порядок рассуждения, а не измерение качества конкретной модели и не описание production-пайплайна.
\n

Отрицательный путь важнее красивого happy path

\n

Рассмотрим учебный parser, который различает ключ, отсутствие значения и недопустимый маркер. На нормальном входе invoice-42 возвращается тот же ключ. Пустая строка означает отсутствие значения. Символ ? означает ошибку. Помощник может предложить вернуть пустую строку для любого нераспознанного значения: позитивный тест останется зелёным, но два разных состояния сольются.

\n
function parseInvoiceKey(input) {\n  if (input === '') return { kind: 'absent' };\n  if (!/^invoice-[0-9]+$/.test(input)) return { kind: 'invalid' };\n  return { kind: 'value', value: input };\n}\n\nconst cases = [\n  ['invoice-42', { kind: 'value', value: 'invoice-42' }],\n  ['', { kind: 'absent' }],\n  ['?', { kind: 'invalid' }]\n];\n\nfor (const [input, expected] of cases) {\n  console.log(input, JSON.stringify(parseInvoiceKey(input)) === JSON.stringify(expected));\n}
\n

Сохраните фрагмент в файл review-example.mjs и запустите node review-example.mjs: он должен вывести три строки с true. Такой результат подтверждает только локальную функцию. Он не доказывает, что вызывающий код не пишет в базу при kind: 'invalid', что регулярное выражение подходит вашему формату или что параллельные запросы безопасны.

\n

Поэтому для реального кода тестируйте не только значение. Заставьте mock write-helper считать вызовы и проверьте ноль вызовов для недопустимого входа. Для повторной операции проверьте идемпотентность и состояние после второго запроса. Для authorization проверьте deny-ветку и отсутствие разрешения по умолчанию. Эти проверки должны быть привязаны к конкретному изменённому пути, иначе зелёный тест может относиться к старой реализации.

\n

Симптом → причина → проверка → действие

\n
Диагностика правдоподобных ошибок в предложенном коде
СимптомВероятная причинаПроверкаДействие
Появился файл вне задачиКонтекст расширился по соседнему кодуСверить каждый path с allowedPaths и владельцемУдалить hunk или оформить отдельное решение
Happy path зелёный, invalid input не описанDefault подменил контрактДобавить таблицу классов входа и негативный тестНе принимать diff до решения владельца
Ошибка возвращается после write-вызоваПроверили output, но не side effectПроверить число и аргументы write-helperВалидировать до записи и покрыть тестом
Добавлен пакет с незнакомым именемМодель предложила несуществующую или неподходящую зависимостьПроверить registry, репозиторий, версию, лицензию и lockfileНе устанавливать до независимой проверки
Удалён падающий тестСимптом скрыли вместо исправления причиныСравнить diff тестов и причину исходного паденияВернуть тест или зафиксировать изменение требования
Линтер и сборка зелёные, результат неверенИнструменты не знают доменный смыслСопоставить branch с contract row и consumerДобавить поведенческий тест и review owner
\n

Проверка зависимости и контекста

\n

Новая зависимость заслуживает отдельной проверки. Убедитесь, что пакет существует в нужном реестре, поддерживает используемую версию runtime, имеет приемлемую лицензию и действительно нужен. Проверьте lockfile после установки и просмотрев diff убедитесь, что транзитивные пакеты не расширили риск неожиданно. Название, которое модель уверенно упомянула, не является фактом.

\n

Точно так же нельзя принимать объяснение «это стандартный API». Откройте документацию именно той версии библиотеки, которой пользуется проект, и найдите сигнатуру, ограничения и пример ошибки. Если помощник сослался на URL, проверьте его отдельно: ссылка должна вести на официальную документацию, а не подтверждать автоматически утверждение из ответа.

\n

Перед отправкой контекста удалите секреты, токены, персональные данные и ненужные фрагменты истории. Конкретные правила хранения и использования запросов зависят от поставщика, тарифного плана и настроек организации. Их нельзя выводить из поведения интерфейса. Для финансовых, медицинских, юридических и security-critical изменений заранее согласуйте допустимый инструмент и обязательный human review.

\n

Порядок действий до merge

\n
  1. Опишите симптом и цену ошибки: какой вход, endpoint или пользователь затронут и какое наблюдаемое поведение сейчас неверно.
  2. Запишите контракт до генерации: нормальный, пустой, невалидный и повторный входы, ожидаемый результат и запрещённый side effect.
  3. Назовите allowed paths, владельца контракта и список запретов: authorization, public format, dependencies, миграции или другие чувствительные границы.
  4. Просмотрите список файлов и hunks командой git diff до чтения объяснения модели. Любое расширение scope остановите.
  5. Сопоставьте каждую изменённую ветку с одной строкой контракта и найдите всех известных consumers через поиск и типы.
  6. Проверьте отрицательный путь: ошибку, число write-вызовов, состояние после отказа, повтор и права доступа, если они участвуют.
  7. Запустите focused test, сборку, lint и доступные security/dependency checks. Отдельно выполните git diff --check.
  8. Попросите reviewer проверить смысл и остаточный риск. Ответ помощника может быть входом в review, но не его результатом.
  9. Зафиксируйте неизвестное: скрытые consumers, нагрузка, совместимость версий, политика данных и то, что не запускалось в текущем окружении.
\n

Ограничения применимости

\n

Метод снижает риск, но не даёт гарантии. Небольшой тестовый набор не покрывает все комбинации, статический анализ не моделирует каждый runtime-путь, а reviewer может не знать скрытого потребителя. Даже официальные рекомендации конкретного поставщика описывают практику использования его продукта, а не корректность вашего доменного контракта.

\n

Для критичного изменения нужны дополнительные меры: владелец предметной области, security review, интеграционный тест, проверка миграции и план отката. Если нельзя проверить права, версию API или происхождение зависимости, правильный результат проверки — остановка и явно названный пробел. Не следует компенсировать отсутствие данных более уверенным prompt.

\n

Учебный parser и команды выше не являются готовым production-рецептом. Они не обращаются к базе, сети, CI или модели и не дают данных о скорости разработки. Их назначение уже: показать, как отделить input, output и forbidden side effect, затем связать их с изменённым кодом. В своём проекте замените значения примера на реальные правила и сохраните их рядом с тестом.

\n

Критерий готовности решения

\n

Решение можно обсуждать на merge, когда reviewer видит пять связей: каждый changed path разрешён задачей; каждая ветка связана с контрактом; отрицательный путь наблюдает и output, и отсутствие запрещённого side effect; зависимости и контекст проверены независимо; владелец принял остаточный риск. Это критерий достаточности свидетельств, а не обещание безошибочности.

\n

Если одна связь не видна, действие должно быть конкретным: сузить diff, добавить тест, проверить пакет, привлечь владельца или остановить изменение. Такая дисциплина сохраняет скорость черновика и не передаёт помощнику ответственность за контракт, которую может принять только команда.

\n

Проверяемые источники

\n" } diff --git a/editorial/agent-rewrites/108.json b/editorial/agent-rewrites/108.json index 3c3306c..5f89731 100644 --- a/editorial/agent-rewrites/108.json +++ b/editorial/agent-rewrites/108.json @@ -1,7 +1,7 @@ { "index": 108, "slug": "editorial-2025-01-practice-ai-coding-assistant", - "title": "AI-помощник в разработке: как принять только проверяемый diff", - "excerpt": "Сгенерированный код может выглядеть готовым и всё же менять не тот контракт. Разбираем ограниченный контекст, отрицательные условия, проверку diff и критерий готовности до merge.", - "contentHtml": "

Разработчик просит помощника исправить обработку ключа. В ответ приходит аккуратный diff: имена понятны, форматирование совпадает с проектом, основной тест проходит. Через день выясняется, что invalid input получает default, а соседний helper меняет право доступа. Симптом заметен не в ответе помощника, а на границе системы: функция вернула допустимую по типам, но неверную по смыслу строку. Цена ошибки — повторная проверка всех затронутых путей, задержка merge и риск выпустить изменение, за которое никто не взял явную ответственность.

\n

Проблема начинается до первого ответа. Запрос без контракта просит правдоподобный текст. Он не говорит, какие файлы разрешено менять, какой результат запрещён, кто принимает расширение области и каким наблюдением подтверждается решение. Поэтому полезный фрагмент легко получает лишние полномочия. Правильная граница выглядит так: помощник предлагает candidate diff, инженер задаёт контракт, reviewer проверяет scope, а тест наблюдает изменённую ветку. Ни один из этих шагов нельзя заменить красивым объяснением.

\n

Механизм: от запроса к решению

\n

У ограниченной задачи есть пять частей. Сначала формулируют один наблюдаемый результат. Затем называют допустимый контекст: сигнатуру функции, строки контракта и связанные тесты. После этого фиксируют отрицательные условия: не менять authorization, не добавлять default, не трогать публичный формат. Владелец принимает смысл изменения. Наконец, команда называет evidence: список путей, contract cases, focused test и человеческий review.

\n

Такой порядок разделяет разные вопросы. Scope отвечает на вопрос что изменилось. Контракт отвечает на вопрос какое поведение допустимо. Тест отвечает на вопрос что произошло на выбранном входе. Review отвечает на вопрос кто принимает остаточный риск. Если один зелёный тест используют как ответ на все четыре вопроса, появляется ложная уверенность.

\n

Симптом → причина → проверка → действие

\n
Четыре типовых сбоя при работе с AI-assisted diff
СимптомПричинаПроверкаДействие
Полезный hunk сопровождается изменением соседнего файлаВ запросе нет allowed paths и запрета на расширениеСверить каждый changed path с карточкой задачиУдалить лишний hunk или открыть отдельное решение
Happy path проходит, invalid input получает новый defaultДо первого ответа не записан отрицательный результатСравнить input-output rows для normal, blank и invalidВернуть diff владельцу контракта
Тест зелёный, но side effect вызывается раньше ошибкиТест наблюдает соседнюю веткуПроверить вызовы helper на duplicate или forbidden inputДобавить focused negative case
Ответ выглядит убедительно, но область риска неизвестнаExplanation приняли за evidenceПеречислить неизвестные consumers, права и версииСузить задачу, привлечь owner или остановить merge
\n

Prompt-card до первого черновика

\n

Карточка не улучшает модель сама по себе. Она делает решение читаемым для инженера и reviewer. Для учебного parser достаточно записать: нормализовать один synthetic invoice key; разрешить только функцию parser и таблицу входов и выходов; не менять authorization, public labels и dependencies; назначить владельца контракта; проверить bounded diff и focused cases. Это не настоящий prompt и не доказательство качества модели. Карточка содержит только фиксированные учебные значения.

\n
const task = {\n  result: 'normalize one invoice key',\n  allowedContext: ['parseInvoiceKey signature', 'fixed input-output rows'],\n  forbidden: ['authorization edits', 'new defaults', 'dependency changes'],\n  owner: 'synthetic-parser-owner',\n  evidence: ['changed paths', 'invalid-input case', 'human review']\n};\n\n// Candidate output is a proposal, not an approval.\n// Any access-policy change stops the review.
\n

Важен не размер diff, а его связь с задачей. Правильная правка иногда меняет два файла: реализацию и тест. Такой diff остаётся bounded, если оба пути названы, а новое поведение следует из контракта. Небольшой diff может быть опасным, если одна строка меняет default, разрешение или смысл ошибки. При обнаружении новой области нельзя задним числом включить её в исходную просьбу. Нужно остановиться, назвать новый риск и получить отдельное решение.

\n
Учебный цикл: prompt-card переходит в bounded diff, затем отдельно проверяются scope, human review и focused test; выход за scope ведёт к остановке.
Рисунок 1. Схема границы между карточкой задачи, candidate diff и проверкой. Подписи и значения synthetic; рисунок не описывает реальный pipeline и не показывает production-результаты.
\n

Конкретный пример: правдоподобная ветка с неверным смыслом

\n

Предположим, parser различает нормальный ключ, пустое значение и недопустимый маркер. Вход invoice-42 даёт invoice-42. Пустой ввод означает отсутствие значения. Маркер ? означает ошибку. Помощник предлагает вернуть пустую строку для любого значения, которое не удалось распознать. Happy path остаётся зелёным. Ошибка скрывается в том, что invalid и absent стали одним состоянием.

\n
const cases = [\n  { input: 'invoice-42', expected: 'invoice-42' },\n  { input: '', expected: 'absent' },\n  { input: '?', expected: 'invalid' }\n];\n\n// Учебный контракт. Он не вызывает реальный parser.\n// Candidate, который сводит '?' к 'absent', отклоняется.
\n

Проверка должна увидеть не только значение. Если duplicate key запрещает запись, test обязан проверить ноль вызовов write helper. Если изменение касается authorization, нужно проверить deny-ветку и запрет на allow по умолчанию. Код может вернуть правильную ошибку после побочного эффекта. Поэтому expected result и forbidden side effect записывают рядом. Линтер проверяет форму. Unit test наблюдает сценарий. Reviewer связывает сценарий с задачей. Эти evidence не складываются в универсальную гарантию.

\n

Порядок проверки до merge

\n
  1. Сформулируйте результат. Запишите один вход, ожидаемый выход и запрещённое побочное действие.
  2. Ограничьте контекст. Передайте только нужную сигнатуру, контрактные строки и тестовые случаи. Уберите секреты, персональные данные и ненужную историю.
  3. Зафиксируйте границу. Назовите allowed paths, запреты и owner. Новое поведение вне карточки остановите.
  4. Отделите candidate diff. Просмотрите список файлов и hunks до чтения объяснения. Ищите лишний путь, новый default, изменение зависимости или прав доступа.
  5. Сверьте контракт. Проверьте normal, blank, invalid и duplicate cases. Для каждой ветки назовите результат и отсутствие forbidden side effect.
  6. Запустите focused checks. Выполните тесты и статический анализ, которые отвечают именно на изменённый вопрос.
  7. Проведите человеческий review. Owner принимает смысл и расширение scope. Reviewer фиксирует comment, request changes или решение принять bounded diff.
  8. Запишите неизвестное. Перечислите других consumers, реальную нагрузку, совместимость версий и security context, если их не проверяли.
\n

Ограничения применения

\n

Этот подход не превращает помощника в источник истины. Ограниченный prompt не знает скрытых consumers, если их не дали в контексте. Тест не доказывает поведение всех комбинаций. Review может пропустить доменную ошибку. Static analysis не заменяет threat model. Чем ближе изменение к authentication, платежам, персональным данным, миграции схемы или внешнему API, тем меньше допустимая область и тем сильнее нужны domain owner, security review и интеграционные проверки. Иногда разумное действие — не применять такой инструмент к чувствительному участку.

\n

Не следует переносить учебный пример в production как готовую библиотеку. Учебный пример не вызывает модель, не обращается к репозиторию, сети, CI или реальным пользователям и не содержит production-метрик. Имена, ключи и outcomes намеренно зафиксированы. Они показывают только форму проверки: сопоставить вход с контрактом, увидеть отрицательный путь и остановить предложение при выходе за границу.

\n

Официальная документация GitHub рекомендует проверять функциональность, контекст, зависимости и AI-specific pitfalls, включая выдуманные API, пропущенные ограничения и удалённые тесты. Это последовательность вопросов, но не сертификат корректности. Профиль NIST для secure software development с generative AI помогает встроить практики безопасности в жизненный цикл. Он не знает доменный контракт и не заменяет решение владельца риска.

\n

Проверяемый критерий готовности

\n

Изменение готово к решению о merge, если reviewer может показать пять вещей: каждый path связан с задачей; каждый changed branch имеет допустимый и запрещённый результат; focused test наблюдает эту ветку; owner назван и принял остаточный риск; неизвестные перечислены отдельно. Если пункт отсутствует, действие должно быть конкретным: сузить diff, добавить evidence, привлечь владельца или остановить merge.

\n

Итог работы с помощником — не удачный ответ и не идеальный prompt. Итог — ограниченное изменение, для которого видно, что изменилось, почему это разрешено и как проверяется отказной путь. Так команда получает скорость черновика без передачи модели ответственности за контракт.

\n

Проверяемые источники

" + "title": "AI-помощник в разработке: как проверить и принять ограниченный diff", + "excerpt": "AI-помощник ускоряет черновик, но не знает скрытый контракт репозитория. Показываю, как ограничить контекст, проверить отрицательные ветки и принять только diff с наблюдаемым evidence.", + "contentHtml": "

Разработчик просит AI-помощника исправить обработку ключа. В ответ приходит аккуратный diff: имена совпадают со стилем проекта, happy path проходит, объяснение звучит уверенно. После merge выясняется, что невалидный маркер превратился в пустое значение, а запись в хранилище выполняется до проверки. Ошибка проявилась не в синтаксисе, а на границе контракта: программа вернула допустимое по типам, но неверное по смыслу значение. Цена такого промаха — повторное ревью, поиск скрытых потребителей и риск изменить права или данные без явного решения владельца.

\n

Безопасная единица работы здесь — не ответ модели, а ограниченный candidate diff. Помощник может ускорить черновик и подсветить варианты, но инженер задаёт допустимый результат, запрещённые изменения и способ наблюдения. Reviewer принимает область и остаточный риск. Тест проверяет конкретные ветки. Если хотя бы одна из этих границ не названа, красивый ответ ещё не является исправлением.

\n

Что именно проверяет команда

\n

У предложения AI есть три разных свойства, которые часто ошибочно объединяют словом «готово». Оно может быть синтаксически корректным, соответствовать локальному стилю и всё же нарушать бизнес-правило. Поэтому сначала разделите вопросы: что предложено, где это изменяет систему, какое поведение разрешено и что наблюдалось на проверочном входе.

\n

Контекст тоже имеет границу. Помощник видит переданные файлы, открытые участки или доступные ему сведения, но не получает автоматически смысл каждого потребителя, права на запись, версию внешнего сервиса и последствия пустого значения. Отсутствующее правило не становится безопасным правилом. Если его нельзя подтвердить в коде, документации или у владельца, его следует записать как неизвестное.

\n
Четыре вопроса перед принятием AI-предложения
СлойВопросНаблюдаемое свидетельствоЧего оно не доказывает
ScopeКакие пути и строки разрешено менять?Список changed paths и diff hunksЧто новое поведение соответствует домену
КонтрактЧто должно произойти для каждого класса входа?Таблица input → output → side effectЧто реализация действительно соблюдает таблицу
ТестЧто произошло на выбранной ветке?Результат теста и наблюдение вызововЧто проверены все потребители, среды и нагрузки
ВладелецКто принимает смысл и остаточный риск?Явное решение domain или security ownerЧто runtime не отличается от тестовой среды
\n

Сначала зафиксируйте границу

\n

До первого запроса запишите одну задачу и её отрицательные условия. «Исправь parser» слишком широко: помощник может изменить формат ошибки, добавить default, обновить зависимость и затронуть соседний обработчик. «Для parseInvoiceKey различай пустой ввод и невалидный маркер; меняй только реализацию и тест; не добавляй default и не трогай авторизацию» уже задаёт проверяемую границу.

\n
const task = {\n  goal: 'parse one invoice key',\n  allowedPaths: ['src/invoice-key.js', 'test/invoice-key.test.js'],\n  contract: [\n    ['invoice-42', 'value:invoice-42', 'no write'],\n    ['', 'absent', 'no write'],\n    ['?', 'invalid', 'no write']\n  ],\n  forbidden: ['new default', 'authorization change', 'dependency change'],\n  owner: 'invoice-contract-owner',\n  evidence: ['path list', 'negative test', 'human review']\n};\n\n// Это карточка задачи, а не разрешение принять любой ответ модели.\n// Выход за allowedPaths останавливает ревью.
\n

Карточка нужна не для красивого prompt, а для сравнения с результатом. В ней должны быть допустимые пути, ожидаемый результат и запрещённый side effect. Секреты, персональные данные и лишнюю историю в контекст не передают. Для изменения авторизации, платежа, миграции схемы или внешнего API границу дополнительно подтверждают владельцем риска; модель не может назначить себе такие полномочия.

\n

Почему правдоподобный diff ошибается

\n

На локальном входе «invoice-42» несколько реализаций выглядят одинаково. Различие появляется на границе: пустая строка — это отсутствие значения, а «?» — ошибка входа. Если функция возвращает '' для обоих случаев, happy path остаётся зелёным, но вызывающий код теряет возможность отличить «не передано» от «повреждено». Это не абстрактный риск генерации: это конкретная потеря состояния.

\n
Схема проверки AI-предложения: карточка задачи задаёт контекст и запреты, candidate diff проходит сверку scope, review и тест; выход за границу ведёт к остановке до merge.
Рисунок 1. Candidate diff проходит четыре независимые проверки. Схема объясняет порядок рассуждения и не является отчётом о работе конкретной модели или production-пайплайна.
\n

Ниже — полностью локальный пример. Он не обращается к модели, репозиторию, сети или реальным данным. Его задача — сделать отрицательную ветку видимой и показать, почему тест на одном положительном значении недостаточен.

\n
node --input-type=module <<'NODE'\nfunction parseInvoiceKey(input) {\n  if (input === '') return { kind: 'absent' };\n  if (!/^invoice-\\d+$/.test(input)) return { kind: 'invalid' };\n  return { kind: 'value', value: input };\n}\n\nconst cases = [\n  [['invoice-42'], { kind: 'value', value: 'invoice-42' }],\n  [[''], { kind: 'absent' }],\n  [['?'], { kind: 'invalid' }],\n];\n\nfor (const [[input], expected] of cases) {\n  const actual = parseInvoiceKey(input);\n  if (JSON.stringify(actual) !== JSON.stringify(expected)) {\n    throw new Error(`${input}: ${JSON.stringify(actual)}`);\n  }\n}\nconsole.log('3 contract cases passed');\nNODE
\n

Команда запускается в shell с установленным Node.js и должна вывести 3 contract cases passed. Версия Node.js, формат запуска и набор тестов — свойства конкретного проекта, поэтому перед копированием примера их сверяют с локальным toolchain. Если candidate заменит ветку invalid на absent, третья проверка упадёт. Если parser вызывается перед записью, одного результата недостаточно: вызывающий код должен доказать, что при invalid write-helper не вызывается.

\n

Читайте diff от границы к смыслу

\n

Сначала просмотрите список файлов, а затем hunks. Не начинайте с объяснения модели: оно может описывать намерение, но не скрытые изменения. Соседний «полезный» файл не становится разрешённым автоматически. Новый импорт, изменение конфигурации, другой обработчик ошибки или удалённый тест — отдельный вопрос для владельца.

\n
# Выполнить из корня git-репозитория после получения candidate diff\ngit diff --name-only\ngit diff --check\ngit diff -- src/invoice-key.js test/invoice-key.test.js\n\n# Затем запустить реальную команду тестов проекта, например:\nnpm test -- --runInBand
\n

git diff --name-only показывает область изменения, а git diff --check находит пробелы и конфликтные маркеры, но ни одна из команд не проверяет доменный смысл. Последняя строка — только форма вызова: флаг --runInBand поддерживается не каждым test runner, поэтому её заменяют на команду, принятую в проекте. Нельзя объявлять тест зелёным, если команда не запускалась или запускала не тот набор файлов.

\n
Симптом candidate diff и следующий шаг
СимптомГипотезаПроверкаРешение
Изменён путь вне карточкиКонтекст расширился самСравнить каждый путь с allowedPathsУбрать hunk или открыть отдельное решение
Happy path зелёный, invalid не описанНеявный default заменил контрактДобавить normal, blank, invalid и duplicate casesНе принимать diff до решения владельца
Ошибка возвращается после write-вызоваПроверен output, но не side effectПроверить число, аргументы и порядок вызововВалидировать до изменения состояния
Добавлена новая зависимостьЛокальная задача превратилась в расширение supply chainПроверить пакет, версию, лицензию и необходимостьУдалить или провести отдельное dependency review
Комментарий модели уверенный, evidence нетОбъяснение приняли за фактПовторить проверку кодом, тестом и документациейОставить решение на hold
\n

Проверяйте не только результат, но и отказ

\n

Негативная проверка должна наблюдать два значения: что вернула функция и чего она не сделала. Для parser это kind: 'invalid' и ноль вызовов записи. Для авторизации — отказ и отсутствие allow по умолчанию. Для миграции — понятная ошибка и сохранение исходного состояния. Для внешнего API — корректная обработка timeout, 4xx и повторного запроса. Название теста должно связывать вход, ожидаемый результат и запрещённое действие.

\n

Проверка зависимостей — отдельный слой. Генератор может предложить несуществующий пакет, неверную версию или код с несовместимой лицензией. Установка зависимости до проверки имени и источника расширяет поверхность атаки и усложняет откат. Поэтому сначала ищут уже используемый механизм в репозитории, затем сверяют официальную документацию пакета и только после этого меняют manifest и lockfile. Если dependency diff не входил в задачу, он остаётся за её границей.

\n

Минимальный маршрут до merge

\n
  1. Опишите один результат. Назовите вход, ожидаемый output и побочный эффект, которого быть не должно.
  2. Сузьте контекст. Передайте нужную сигнатуру, контракт, связанные тесты и правила проекта; исключите секреты и лишнюю историю.
  3. Зафиксируйте allowedPaths. Список файлов и запреты должны появиться до генерации. Любой новый путь остановите.
  4. Просмотрите diff. Проверьте файлы, импорты, defaults, удалённые тесты, зависимости, права и изменения публичного формата.
  5. Сверьте четыре класса входа. Проверьте normal, blank, invalid и duplicate или retry, если они возможны в вашем домене.
  6. Наблюдайте side effect. Убедитесь, что отказ не вызывает запись, отправку, выдачу права или повторную операцию.
  7. Запустите focused checks. Используйте реальные команды проекта и сохраните результат; затем добавьте интеграционные, security или performance checks по риску.
  8. Проведите человеческое ревью. Владелец контракта принимает смысл, reviewer — scope и читаемость, security или data owner подключается при чувствительном изменении.
  9. Запишите неизвестное. Отдельно перечислите непроверенных consumers, версии, нагрузку и различия сред. Не превращайте отсутствие данных в зелёный статус.
\n

Границы применения

\n

Этот маршрут уменьшает риск, но не делает генерацию источником истины. Ограниченный контекст не раскрывает скрытого потребителя. Unit-тест проверяет выбранные случаи, а не все комбинации. Линтер и типы подтверждают форму интерфейса, но не смысл бизнес-правила. Человеческое ревью тоже ошибается, особенно если владелец контракта не участвует.

\n

К критическим участкам применяйте более строгий процесс. Для authentication, платежей, персональных данных, медицинских решений, миграций и необратимых операций нужны дополнительные владельцы, threat model, интеграционные проверки и понятный rollback. Не передавайте внешнему сервису секреты и фрагменты кода, если политика проекта этого не разрешает. Правила хранения, обучения и удаления данных зависят от конкретного инструмента и тарифа; их нельзя выводить из общего слова «AI».

\n

Официальные рекомендации GitHub сводят ревью AI-кода к функциональным проверкам, сверке контекста и намерения, проверке зависимостей, поиску выдуманных API и пропущенных ограничений, совместному ревью и автоматизации. Там же прямо сказано, что предложения нужно проверять и тестировать, особенно для критичных и чувствительных приложений. NIST SP 800-218A дополняет SSDF практиками для разработки систем с generative AI и предназначен для применения вместе с SSDF 1.1. Это рамки и направления проверки, а не готовый тест вашего репозитория.

\n

Критерий готовности

\n

Candidate diff можно выносить на решение о merge, когда для каждого изменённого пути виден scope, для каждой ветки есть контрактная строка, отказной путь проверяет output и side effect, а тесты действительно запускались на изменённой реализации. Владелец назван и принял остаточный риск. Неизвестные потребители, версии и среда перечислены отдельно. Если одного элемента нет, действие однозначно: сузить diff, добавить evidence, привлечь владельца или остановить merge.

\n

Польза помощника — в скорости перебора вариантов, а не в передаче ему ответственности. Надёжное решение оставляет после себя читаемый diff, воспроизводимую проверку и понятную причину, по которой изменение разрешено. Такой результат можно проверить через неделю другим инженером и отличить от правдоподобной, но неверной догадки.

\n

Проверяемые источники

" } diff --git a/editorial/agent-rewrites/109.json b/editorial/agent-rewrites/109.json index 94509cf..a5b2044 100644 --- a/editorial/agent-rewrites/109.json +++ b/editorial/agent-rewrites/109.json @@ -1,7 +1,7 @@ { "index": 109, "slug": "editorial-2024-12-field-maintenance-retro", - "title": "Год сопровождения: как решить, продолжить, остановить или перепроверить", - "excerpt": "Повторяемая ошибка в сопровождении редко требует немедленной переделки. Сначала отделите симптом от причины, назовите владельца риска и поставьте проверяемую границу для следующего действия.", - "contentHtml": "

В конце квартала список сопровождения выглядит знакомо: одна и та же ручная проверка, старый риск совместимости и задача на удаление, которую откладывают. Симптомы повторяются, но решение каждый раз начинается с нуля. Цена ошибки — не только лишний час инженера. Команда может удалить ещё используемый маршрут, принять риск без владельца или назвать договорённость исправлением.

\n

Тезис этой статьи простой: годовое сопровождение нужно вести как последовательность проверяемых решений. Для каждой проблемы сначала фиксируют симптом и границу, затем решают: продолжить узкий эксперимент, остановить рост области работ или перепроверить устаревшее свидетельство. Это не обещает результата в production. Такой порядок не даёт черновому решению получить права на изменение живой системы.

\n

Начните с наблюдаемого симптома

\n

Запись «в системе накопился технический долг» ничего не проверяет. Запись «при диагностике одного типа отказа инженер каждый раз ищет один и тот же параметр в трёх местах» уже задаёт наблюдение. У него есть действие, граница и возможный следующий шаг.

\n

Ретроспектива сопровождения не заменяет postmortem. Postmortem описывает подтверждённое событие, воздействие, причины и follow-up. Если инцидента не было, нельзя добавлять в текст ущерб, время восстановления или результат исправления. Для годового обзора достаточно назвать повторяемый симптом, неизвестное и решение, которое можно проверить отдельно.

\n

Механизм: timeline и граница решения

\n

Полезная карточка сопровождения содержит четыре точки. T0 — наблюдение. T1 — ограниченная гипотеза или эксперимент. T2 — повторная проверка свидетельства. T3 — решение продолжить, изменить формулировку или остановиться. Такая шкала не изображает календарь реальной команды. Она показывает порядок знаний.

\n
\"Лента
Схема показывает порядок проверки. Она не описывает реальные события, deployment или rollback.
\n

Граница решения отвечает на вопрос «что именно мы сейчас можем утверждать». Например, можно утверждать, что диагностический шаг повторяется в учебной карточке. Нельзя утверждать, что он уже уменьшил нагрузку на поддержку. Можно увидеть отсутствие роли-владельца. Нельзя считать риск принятым. Можно сохранить вопрос о восстановлении. Нельзя объявлять cleanup безопасным до проверки зависимостей.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Один диагностический шаг снова объясняют вручнуюУ runbook нет явной границы остановкиДругой инженер находит symptom, check и stop condition по одной записиПродолжить один узкий runbook-эксперимент
Риск совместимости описан, но owner не названНаблюдение приняли за решениеПроверить роль, которая может принять residual riskОстановить расширение scope
Cleanup выглядит безопасным по старой карточкеНеизвестны consumers и путь восстановленияПерепроверить dependency graph, data conditions и restore boundaryНе переходить к удалению
В отчёте появился измеренный эффект без источникаУчебный вывод смешали с production-фактомНайти trace, метрику, журнал или убрать утверждениеОставить только подтверждённое наблюдение
\n

Три решения на одном годовом обзоре

\n

Продолжить: повторяемый пробел в runbook

\n

Представьте учебную карточку: на трёх проверках инженер повторно спрашивает, где заканчивается диагностический путь. Это не доказывает частоту проблемы в реальной системе. Но факт повторения в карточке оправдывает небольшой эксперимент: добавить один boundary, один способ проверки и один stop condition.

\n

Эксперимент готов, если другой читатель проходит фиксированный failing path и получает тот же порядок действий без доступа к авторским пояснениям. Если задача разрастается до redesign поддержки или начинает обещать экономию времени, её нужно остановить и оформить как отдельное решение. Runbook не должен незаметно стать программой перестройки.

\n

Остановить: риск без владельца

\n

Вторая карточка описывает границу контракта, но не содержит роли, которая принимает остаточный риск. В такой ситуации фраза «продолжаем миграцию» подменяет решение намерением. Отсутствие известных consumers тоже не равно доказанному отсутствию consumers.

\n

Правильный следующий шаг — остановить рост области работ и задать один вопрос: кто может принять или отклонить утверждение о совместимости именно этой границы? Пока роль не названа и не имеет полномочий, карточка не должна переходить в rollout, removal или обещание обратной совместимости.

\n

Перепроверить: cleanup ещё не план отката

\n

Третья карточка выглядит спокойной: есть предложение удалить старый объект и короткое описание риска. Но неизвестны зависимости, совместимость данных и успешность восстановления. Слово «cleanup» скрывает изменение состояния. Его нельзя считать обратимым только потому, что действие кажется маленьким.

\n

Recheck должен назвать boundary, факт остановки и путь возврата. Для маршрута это может быть прежняя конфигурация и проверка ответа клиента. Для данных — совместимая схема и проверка чтения. Для зависимости — список потребителей и подтверждённый владелец. Если эти условия неизвестны, draft не превращается в rollback plan.

\n

Учебный пример stop path

\n

Ниже — намеренно маленькая модель. Она не читает репозиторий, не вызывает сеть, не выполняет deployment и не откатывает изменения. Её задача — показать, что решение остановиться меняет только статус черновика.

\n
const card = {\n  symptom: 'cleanup proposed',\n  known: ['restore question exists'],\n  unknown: ['consumers', 'data compatibility', 'rollback check'],\n};\n\nfunction plan(card) {\n  const needsRecheck = card.unknown.length > 0;\n  return { decision: needsRecheck ? 'recheck' : 'continue', realChange: false };\n}\n\nconst draft = plan(card);\nconsole.log(draft.decision);  // recheck\nconsole.log(draft.realChange); // false
\n

Пример учебный. Он не определяет риск автоматически и не доказывает, что список неизвестных полон. В реальном проекте поля нужно связать с разрешёнными источниками, владельцем решения и конкретным тестом. Если код начинает сам удалять, менять или откатывать состояние, он вышел за границу этой модели.

\n

Как отличить факт от решения

\n

Факт можно показать другому человеку: записью события, конфигурацией, тестовым входом, ссылкой на код или повторяемым действием. Решение добавляет владельца и условие следующего шага. Гипотеза связывает факт с возможной причиной. Нельзя заменить один тип другим.

\n
Факт: один шаг диагностики повторяется в учебной карточке.\nГипотеза: runbook не показывает boundary.\nПроверка: другой читатель проходит тот же failing path.\nРешение: продолжить один эксперимент, не менять production.
\n

Если подтверждающего источника нет, пишите «неизвестно». Это полезнее, чем округлённая оценка. Неизвестное задаёт следующий вопрос. Выдуманная точность создаёт ложное разрешение на действие.

\n

Порядок работы с одной карточкой

\n
  1. Сузьте scope. Оставьте один симптом и одну границу. Не объединяйте cleanup, миграцию и изменение контракта в одну карточку.
  2. Восстановите T0–T3. Для каждой точки запишите только известный артефакт и отдельный список неизвестного.
  3. Назовите цену ошибки. Укажите, что произойдёт при неверном решении: повторная ручная работа, отказ потребителя, потеря совместимости или невозможность восстановления.
  4. Проверьте owner boundary. Найдите роль, которая может принять риск или остановить действие. Если роли нет, остановите расширение scope.
  5. Выберите один исход. Continue оставляет ограниченный эксперимент. Revise меняет карточку после новой проверки. Stop отбрасывает draft до отдельного разрешённого решения.
  6. Запишите отрицательный путь. Укажите факт, который блокирует rollout, removal или обещание результата. Не заменяйте его словом «проверим позже».
  7. Повторите проверку. Используйте тот же вход и ту же границу. Если условия изменились, это новая карточка, а не тихое продолжение старой.
\n

Ограничения

\n

Эта схема не измеряет надёжность и не ранжирует весь backlog. Она не заменяет incident response, change control, threat model, интеграционные тесты и право владельца на изменение системы. Один runbook-эксперимент не доказывает снижение toil. Один найденный owner не доказывает совместимость. Один restore question не доказывает успешный rollback.

\n

Учебные карточки, T0–T3 и код выше вымышлены. В статье нет production-метрик, истории конкретной команды, данных клиентов, deployment или результата исправления. Переносить вывод можно только после замены учебного входа фактическими источниками и проверки условий среды.

\n

Проверяемый критерий готовности

\n

Карточка готова к следующему решению, если читатель видит один наблюдаемый симптом, одну границу, цену ошибки, известное и неизвестное, роль владельца, отрицательный путь и один следующий эксперимент. Для cleanup дополнительно указаны зависимости, условия данных и проверка восстановления. Для риска без owner итогом должен быть stop, а не скрытое продолжение.

\n

Год сопровождения не закрывается красивым списком исправлений. Он закрывается набором границ, которые можно повторно проверить. Если новая проверка не может изменить решение, она не проверяет механизм.

\n

Проверяемые источники

\n" + "title": "Год сопровождения: как принять решение по повторяющейся проблеме", + "excerpt": "Практическая схема для повторяющихся проблем сопровождения: отделить факт от гипотезы, проверить границу риска и выбрать между ограниченным экспериментом, остановкой и перепроверкой.", + "contentHtml": "

Повторяющаяся ошибка сопровождения редко требует немедленной переделки всей системы. Сначала нужно выяснить, что именно наблюдается, какой риск уже подтверждён и какое действие запрещено до следующей проверки. Иначе команда легко примет старую запись за доказательство, удалит ещё используемый маршрут или назовёт договорённость исправлением.

\n

Разберём рабочую схему для годового обзора: одна карточка — один симптом, одна граница и одно следующее решение. На выходе может быть ограниченный эксперимент, остановка работ или перепроверка старого свидетельства. Сценарии и код ниже учебные: они показывают способ рассуждения, но не описывают production-события, метрики или результат конкретной команды.

\n

Начните с наблюдаемого симптома

\n

Фраза «в проекте накопился технический долг» не даёт воспроизводимой проверки. Её лучше заменить записью, которую другой инженер может увидеть и повторить: «при разборе отказа параметр ищут в трёх местах», «после изменения конфигурации нет проверки размера входного файла», «описание совместимости не называет владельца остаточного риска».

\n

У симптома должны быть четыре поля: действие, вход, наблюдаемый результат и граница. Например: инженер открывает один и тот же runbook, использует тестовую запись с идентификатором case-17, не находит условия остановки и не меняет систему. Последняя часть важна: отсутствие изменения — тоже факт, если его можно подтвердить журналом или diff.

\n

Не смешивайте ретроспективу сопровождения с postmortem (разбором инцидента). Google SRE описывает postmortem как запись события, воздействия, принятых мер, причин и последующих действий. Если подтверждённого инцидента не было, в карточке нельзя придумывать простой, время восстановления или эффект исправления. Для повторяемого пробела достаточно назвать источник наблюдения и следующий безопасный тест.

\n

Четыре точки решения: T0–T3

\n

Карточка становится полезной, когда показывает не только мысль автора, но и переход от знания к действию. Используйте четыре точки: T0 — наблюдение; T1 — узкая гипотеза и эксперимент; T2 — повторная проверка источника и границы; T3 — решение продолжить, остановить или изменить формулировку.

\n
\"Схема
Последовательность отделяет проверку знания от изменения системы. Она не изображает календарь реального проекта и не означает выполненный rollback.
\n

На T0 не добавляйте объяснение, которого нет в источнике. На T1 не расширяйте эксперимент до миграции или redesign. На T2 повторите тот же вход и проверьте, что источник действительно относится к текущей версии и потребителям. На T3 зафиксируйте отрицательный путь: какое условие блокирует rollout, удаление или обещание результата.

\n
Как перевести наблюдение в проверяемое решение
НаблюдениеЧто нужно проверитьБезопасный следующий шагЧто пока запрещено утверждать
Один диагностический шаг снова объясняют вручнуюДругой инженер находит вход, проверку и условие остановки в одной карточкеПродолжить один эксперимент по runbookЧто toil уже уменьшился или ошибка исчезла в production
Риск совместимости описан, но владелец не названКакая роль может принять или отклонить остаточный рискОстановить расширение области работЧто миграция разрешена или потребители отсутствуют
Предлагается удалить старый объектПотребителей, условия данных и проверку восстановленияПерепроверить зависимость и путь возвратаЧто cleanup обратим только из-за малого diff
Появилось число без трассы, журнала или метрикиИсточник числа и способ повторного измеренияОставить только подтверждённое наблюдениеЧто число описывает эффект исправления
\n

Когда продолжать: узкий эксперимент

\n

Продолжение оправдано, когда симптом повторяется, граница понятна, а проверка не меняет живое состояние. Например, в трёх учебных прогонах читатель не понимает, где заканчивается диагностический путь. Это не доказывает частоту проблемы у пользователей, но достаточно для маленького эксперимента: добавить в runbook один вход, один диагностический шаг и одно условие остановки.

\n

Критерий эксперимента должен быть бинарным и наблюдаемым. Другой читатель либо проходит фиксированный failing path без устного пояснения, либо нет. Не подменяйте критерий обещанием «сэкономить время»: экономию можно заявлять только после согласованного измерения с определёнными входом, периодом и базовой линией.

\n

Ограничение scope защищает от незаметного роста задачи. Если для исправления карточки понадобились новая схема данных, новый контракт или массовая миграция, эксперимент закончился. Новая работа получает отдельную оценку риска, владельца и план проверки. Она не наследует разрешение от маленького runbook-изменения.

\n

Когда остановиться: риск без владельца

\n

Запись «продолжаем миграцию, совместимость проверим позже» не является решением. В ней отсутствуют полномочия и блокирующее условие. Наличие зелёного теста на одном потребителе также не доказывает, что известны все потребители или что остаточный риск принят.

\n

Остановите расширение работ, если неизвестно, кто может принять риск, какие клиенты зависят от границы и что произойдёт при отказе. Зафиксируйте конкретный вопрос: «какая роль подтверждает совместимость маршрута /legacy с клиентами версии v2?». Пока ответа и источника нет, не следует менять контракт, удалять обратную совместимость или объявлять rollout безопасным.

\n

Такой stop не означает, что система сломана. Он означает, что имеющихся данных недостаточно для выбранного действия. Это различие помогает не превращать неопределённость в срочную задачу без владельца.

\n

Когда перепроверить: cleanup не равен rollback

\n

Удаление конфигурации, поля или старой зависимости меняет состояние, даже если diff занимает одну строку. Перед ним нужны как минимум три независимые проверки: список потребителей, совместимость данных и проверяемый путь восстановления. Если восстановление описано словами «вернуть назад», это ещё не rollback plan.

\n

Для маршрута проверка может включать поиск обращений, тест старого клиента и возврат прежней конфигурации в изолированной среде. Для данных — совместимую схему, контроль чтения и восстановление копии на тестовом наборе. Для зависимости — граф импорта, сборку и подтверждение владельца потребителя. Набор проверок зависит от архитектуры; универсального безопасного числа нет.

\n

Практический порог простой: если новый факт способен изменить решение «удалять или оставить», его нужно получить до удаления. Google SRE рекомендует для неаварийных изменений поэтапный rollout, наблюдение и откат при неожиданном поведении. Это ориентир процесса, а не разрешение копировать чужие проценты трафика или порядок релиза.

\n

Воспроизводимая модель без изменения системы

\n

Ниже — полностью локальный пример на Node.js. Он принимает карточку, проверяет наличие неизвестных полей и печатает решение. Сохраните код в файл maintenance-check.mjs, затем выполните команды. Скрипт не читает репозиторий, не вызывает сеть и не удаляет данные.

\n
const card = {\n  symptom: 'cleanup proposed',\n  known: ['restore question exists'],\n  unknown: ['consumers', 'data compatibility', 'rollback check'],\n};\n\nfunction decide(item) {\n  if (!item.symptom) return { decision: 'stop', reason: 'no observable symptom' };\n  if (item.unknown.length > 0) {\n    return { decision: 'recheck', reason: item.unknown.join(', ') };\n  }\n  return { decision: 'continue', reason: 'bounded experiment only' };\n}\n\nconsole.log(decide(card));
\n
node --version\nnode maintenance-check.mjs\n# { decision: 'recheck', reason: 'consumers, data compatibility, rollback check' }
\n

Ожидаемый вывод зависит от версии Node.js и формата консоли, но решение и список причин должны совпасть. Добавьте неизвестное поле, например owner, и убедитесь, что решение не изменилось на continue. Удалите все элементы из unknown только после того, как для каждого есть источник и повторяемая проверка. Модель намеренно не оценивает полноту риска: это обязанность владельца системы и её процесса изменений.

\n

Как проверять источники и границы утверждения

\n

Для каждой строки карточки заведите пару «утверждение — источник». Источником может быть журнал, тестовый вход, версия конфигурации, diff, трасса или официальная документация. Ссылка на общий раздел проекта без указания версии и операции не подтверждает конкретный вывод.

\n

Удобная проверка в Git-репозитории выглядит так:

\n
git grep -n -- '/legacy' -- ':!vendor'\ngit log -S'/legacy' --all --oneline -- path/to/config\ngit diff --check
\n

В этих командах /legacy, path/to/config и исключение vendor — placeholders: замените их на строку и путь своего проекта. Первая команда ищет текущие обращения, вторая помогает найти историю строки, третья проверяет пробельные ошибки в diff. Ни одна из них не доказывает отсутствие динамических потребителей, внешних клиентов или данных в хранилище. Для них нужны отдельные источники.

\n

Официальная документация задаёт рамку, но не заменяет локальное доказательство. NIST описывает оценку риска как часть процесса управления риском, который помогает выбрать действие по выявленному риску. Это не превращает абстрактную оценку в разрешение на изменение конкретного сервиса. Точно так же рекомендации Google SRE по rollout применимы как принцип наблюдаемого и постепенного изменения, но параметры должны соответствовать вашей нагрузке, правам и плану восстановления.

\n

Порядок годовой проверки одной карточки

\n
  1. Сузьте scope. Оставьте один симптом, один объект и одну границу. Не соединяйте cleanup, изменение контракта и миграцию в одну запись.
  2. Зафиксируйте T0. Запишите вход, наблюдаемый результат, время или версию источника и то, что не менялось.
  3. Разделите известное и неизвестное. Для каждого неизвестного сформулируйте вопрос, источник и критерий достаточности.
  4. Назовите цену ошибки. Опишите конкретное последствие: отказ клиента, потеря совместимости, повторная ручная работа или невозможность восстановления.
  5. Составьте T1. Выберите минимальную проверку, которая различает две гипотезы и не меняет production-состояние.
  6. Проведите T2. Повторите тот же вход, проверьте версию и независимость источника. Если условия изменились, откройте новую карточку.
  7. Примите T3. Continue оставляет ограниченный эксперимент, stop блокирует действие без владельца или доказательства, recheck возвращает карточку на проверку границы.
  8. Запишите отрицательный путь. Укажите, какое наблюдение остановит rollout или удаление, кто увидит сигнал и какое действие допустимо после него.
\n

Ограничения применимости

\n

Схема полезна для повторяющихся задач сопровождения, runbook и небольших изменений конфигурации. Она не заменяет аварийное реагирование, управление изменениями, threat model, интеграционные тесты, резервное копирование, требования безопасности или полномочия владельца сервиса.

\n

Точки T0–T3 и код — учебная модель. В них нет production-метрик, данных клиентов, реального deployment или доказанного результата. Один найденный владелец не доказывает совместимость. Один успешный тест не доказывает отсутствие потребителей. Один вопрос о восстановлении не доказывает успешный rollback. Для критичных систем добавьте требования из своей политики, регуляторные ограничения и независимое ревью.

\n

Официальные источники ниже описывают общие практики и федеральный контекст NIST, а не конкретную архитектуру вашего проекта. Перед применением проверьте версию инструмента, модель доступа, допустимое окно изменения и способ безопасно вернуть состояние.

\n

Критерий готовности

\n

Карточка готова к следующему решению, когда другой инженер видит один симптом, источник, границу, известное и неизвестное, цену ошибки, роль владельца, проверяемый эксперимент и отрицательный путь. Для удаления дополнительно указаны потребители, условия данных и тест восстановления. Для риска без владельца итогом остаётся stop, а не скрытое продолжение.

\n

Год сопровождения закрывается не количеством закрытых задач, а повторяемостью решений. Если новый факт не может изменить выбранный исход, проверка не затрагивает механизм. Если исход меняется после проверки, это полезный результат: он показывает, где прежняя уверенность была шире доказательства.

\n

Проверяемые источники

\n" } diff --git a/editorial/agent-rewrites/110.json b/editorial/agent-rewrites/110.json index bb64da1..68d8239 100644 --- a/editorial/agent-rewrites/110.json +++ b/editorial/agent-rewrites/110.json @@ -3,5 +3,5 @@ "slug": "editorial-2024-12-mechanism-maintenance-retro", "title": "Как приоритизировать сопровождение без ложной точности", "excerpt": "Maintenance backlog становится полезным, когда отделяет наблюдаемый факт от оценки. Разбираем evidence, повторяемость и uncertainty, чтобы выбрать следующую проверку и не принять учебный score за прогноз.", - "contentHtml": "

В maintenance backlog появляется строка «риск 8,7», но никто не может показать исходные данные. В соседней карточке зафиксирован один и тот же симптом после нескольких проверок, но числового score нет. Команда выбирает первую задачу: число выглядит точнее. Цена ошибки — повторяемый дефект остаётся без проверки, а спорная оценка получает вид готового решения. Позже приходится восстанавливать границу риска, владельца и основание приоритета.

Тезис: сопровождение нужно приоритизировать по проверяемой границе, а не по красивой арифметике. Сначала отделите evidence от оценки, repeatability от впечатления, а uncertainty от нулевого риска. Затем используйте score только как фильтр очереди. Он может выбрать следующую проверку, но не доказывает вероятность сбоя, денежную экономию или необходимость изменения.

Механизм: факт, сигнал, граница действия

Evidence — факт, который можно предъявить другому инженеру: запись проверки, contract, тест, runbook или повторная карточка. Signal — признак, помогающий выбрать следующий вопрос. Priority boundary — правило, которое переводит карточку в одно из состояний: проверить сейчас, подготовить к следующему review или наблюдать. Эти понятия нельзя заменять одним числом.

Для каждого item нужны четыре поля. Первое — symptom: что наблюдаем и где. Второе — boundary: какой потребитель, интерфейс или операция затронуты. Третье — evidence: что уже известно и чего нет. Четвёртое — action: какую одну проверку можно выполнить без расширения scope. Если action требует сначала узнать владельца, собрать неизвестный граф зависимостей и изменить код, карточка описывает не задачу, а гипотезу.

Повторяемость тоже имеет границу. Можно считать повтором только тот же diagnostic step, тот же вопрос совместимости или тот же recheck gate. Фразы «мы часто к этому возвращаемся» недостаточно. Если boundary меняется от review к review, события нельзя складывать в один показатель.

Учебная heatmap сопоставляет impact и repeatability, а uncertainty показывает отдельную границу проверки
Учебная heatmap помогает расположить карточки по двум осям. Она задаёт порядок review, но не показывает вероятность отказа и не считает бюджет.

Какие evidence нельзя смешивать

Тип evidence и допустимый вывод
ТипЧто зафиксированоЧего это не доказываетСледующий вопрос
Known artifactЕсть конкретный тест, contract, runbook или запись проверкиЧто artifact покрывает всех потребителей и все путиКакую именно границу он покрывает?
Leading signalДо изменения виден ранний признак роста риска или scopeЧто отказ уже произошёл или обязательно произойдётКакой stop condition сработает раньше?
Lagging signalОдин и тот же вопрос вернулся после reviewЧто известна первопричина или будущая частотаЧто прошлое действие не изменило?
UnknownДанных недостаточно или метод проверки недоступенЧто риск равен нулю и число можно угадатьКакой разрешённый метод изменит статус?

Known artifact сужает область незнания, но не закрывает её автоматически. Leading signal позволяет остановить расширение scope до изменения системы. Lagging signal показывает повторение, но не объясняет причину. Unknown — не пустая клетка. Это явное условие, при котором нельзя усиливать вывод.

Такой подход согласуется с практикой оценки риска: оценка помогает выбрать курс действий, но не заменяет решение владельца. Если неизвестное исчезает при переносе строки в таблицу, формула начинает работать с ложным входом. Сначала сохраните текст неизвестного. Потом решайте, нужен ли отдельный способ его проверить.

Что можно измерять без фальшивой цены

У maintenance item бывает наблюдаемый класс издержки: повторное ручное объяснение, задержка review, дополнительная проверка, возврат к той же границе. Такой label честнее, чем «экономия 14 часов», если команда не зафиксировала период, выборку, метод подсчёта и право использовать эти данные.

Денежная оценка требует отдельного контракта. Нужны scope, период, источник, правило attribution и человек, который принимает допущения. Без них точное число создаёт асимметрию: карточка с выдуманной суммой выигрывает у карточки с честным unknown. Для порядка review достаточно сказать, какой повторяемый шаг мешает работе и как его можно проверить.

Учебная модель priority boundary

Ниже — учебный пример. Он работает только с заранее заданными метками impact, repeatability, uncertainty и evidenceStrength. Он не читает production-метрики, не использует историю инцидентов и не предсказывает результат. Формула нужна для прозрачного разговора о входах:

function teachingPriority(card) {\n  const score = card.impact * card.repeatability\n    + card.uncertainty * 2\n    - card.evidenceStrength;\n\n  const boundary = score >= 10\n    ? 'review-now'\n    : score >= 6\n      ? 'plan-next-review'\n      : 'watch-and-recheck';\n\n  return { score, boundary };\n}\n\n// Учебный объект в памяти. Не production-метрика.\nconst example = teachingPriority({\n  impact: 3,\n  repeatability: 2,\n  uncertainty: 2,\n  evidenceStrength: 1,\n});\n// { score: 9, boundary: 'plan-next-review' }

Число 9 в этом фрагменте ничего не говорит о вероятности сбоя. Оно только показывает, как выбранные teaching labels переводят карточку в учебную полосу. Если reviewer не согласен с uncertainty или evidenceStrength, спорить нужно с входом и его границей, а не с десятичными знаками.

В реальном проекте такой score можно применять только после явного согласования шкал и источников. Если у карточки появился traffic, incident count или денежная оценка, это не повод молча добавить поле в объект. Нужно пересмотреть модель и правила доступа к данным. Иначе учебная функция начинает изображать систему, которой она не видела.

Симптом → причина → проверка → действие

Диагностика maintenance-приоритета
СимптомПричинаПроверкаДействие
Карточка с высоким score не имеет фактаОценку приняли за evidenceПопросить ссылку на тест, контракт, incident или повторную записьПонизить вывод до unknown и назначить отдельную проверку
Один симптом возвращается на каждом reviewПовторяемая граница не названаСравнить diagnostic step, consumer и recheck gateСформулировать одну bounded проверку, не начинать rewrite
Unknown исчез после расчётаПропуск данных заменили нулёмСверить исходную карточку и обязательные поля моделиОстановить score и сохранить причину неизвестности
Leading signal требует немедленного deployРанний признак перепутали с доказанным отказомПроверить symptom, affected boundary и stop conditionОграничить следующий шаг review или тестом
Lagging signal лечат автоматизациейПовторение приняли за первопричинуОткрыть прошлую карточку и проверить, что изменилосьСначала уточнить owner, evidence и действие, затем выбирать автоматизацию
Денежная сумма определяет очередьНет периода, метода или attributionПроверить источник, выборку, допущения и полномочияВернуть cost к наблюдаемому классу до отдельной оценки

Отрицательный путь важнее красивого score

Проверка должна уметь остановиться. Если карточка не содержит boundary, owner role или evidence, результатом не должен быть score с нулевыми значениями. Ноль означает измеренное отсутствие, а unknown означает отсутствие знания. Эти состояния нельзя смешивать.

Остановите draft, если scope вырос с одной проверки до переписывания подсистемы, если action не имеет stop condition или если неизвестный consumer влияет на решение. Не объявляйте item закрытым после одной удачной проверки. Успешный путь показывает, что выбранный вход обработан. Отрицательный путь показывает, что опасный вход не превратился в разрешение на изменение.

Для lagging signal отдельно сравните прошлое и текущее действие. Если symptom вернулся, спросите, изменился ли contract, owner, evidence или stop condition. Если ничего не изменилось, повторная формулировка задачи не является прогрессом. Если изменилось только название, карточку нужно вернуть в review.

Порядок работы

  1. Опишите symptom. Укажите наблюдаемый факт, место и цену ошибки без предположения о причине.
  2. Назовите boundary. Зафиксируйте consumer, интерфейс, диагностический шаг или recheck gate.
  3. Разделите evidence. Отметьте known artifact, leading signal, lagging signal и unknown отдельно.
  4. Выберите один action. Он должен проверять границу и иметь stop condition без изменения production.
  5. Назначьте повторяемость. Считайте повтором только одинаковый diagnostic step или тот же recheck gate.
  6. Проверьте score. Если применяете учебную шкалу, покажите все входы и не называйте результат вероятностью или ценой.
  7. Передайте decision owner. Он выбирает review-now, plan-next-review или watch-and-recheck с учётом остаточного риска.
  8. Зафиксируйте результат. Запишите, какое знание изменилось, какой путь остановился и что проверять при следующем review.

Ограничения

Heatmap и формула не заменяют risk assessment, change approval, тесты, rollback и наблюдение системы. Они не знают реальный traffic, SLO, support queue, стоимость простоя или число пользователей. Учебный код не подключается к данным и не подтверждает, что выбранные коэффициенты подходят вашему проекту.

Preventive maintenance не всегда нужно автоматизировать. Ручной review может быть обязательным из-за безопасности, прав доступа или редкой операции. Повторяемость сама по себе не доказывает, что автоматизация окупится. Она только помогает найти шаг, который стоит разобрать.

Нельзя выдавать score за вероятность incident. Нельзя считать отсутствие evidence доказательством отсутствия риска. Нельзя включать реальный incident или денежную оценку в учебную модель без нового контракта, метода и ответственного владельца. При такой неопределённости правильное действие — остановить расширение scope.

Проверяемый критерий готовности

Карточка готова к следующему review, если другой инженер может восстановить symptom, boundary, тип сигнала, evidence, неизвестное, один action и stop condition. Для каждого значения понятно, откуда оно взялось. Для каждого перехода между полосами понятна причина. Повторная проверка на том же входе даёт тот же статус.

Если используется score, рядом лежат шкала, формула и явная оговорка о её учебном или локальном статусе. Отрицательная проверка переводит карточку в hold или unknown, а не в нулевой риск. Готовность означает не «задача решена», а «следующий шаг ограничен, проверяем и не маскирует неизвестное».

Проверяемые источники

" + "contentHtml": "

В maintenance backlog появляется строка «риск 8,7», но никто не может показать исходные данные. В соседней карточке зафиксирован один и тот же симптом после нескольких проверок, но числового score нет. Команда выбирает первую задачу: число выглядит точнее. Цена ошибки — повторяемый дефект остаётся без проверки, а спорная оценка получает вид готового решения. Позже приходится восстанавливать границу риска, владельца и основание приоритета.

Тезис: сопровождение нужно приоритизировать по проверяемой границе, а не по красивой арифметике. Сначала отделите evidence от оценки, repeatability от впечатления, а uncertainty от нулевого риска. Затем используйте score только как фильтр очереди. Он может выбрать следующую проверку, но не доказывает вероятность сбоя, денежную экономию или необходимость изменения.

Механизм: факт, сигнал, граница действия

Evidence — факт, который можно предъявить другому инженеру: запись проверки, contract, тест, runbook или повторная карточка. Signal — признак, помогающий выбрать следующий вопрос. Priority boundary — правило, которое переводит карточку в одно из состояний: проверить сейчас, подготовить к следующему review или наблюдать. Эти понятия нельзя заменять одним числом.

Для каждого item нужны четыре поля. Первое — symptom: что наблюдаем и где. Второе — boundary: какой потребитель, интерфейс или операция затронуты. Третье — evidence: что уже известно и чего нет. Четвёртое — action: какую одну проверку можно выполнить без расширения scope. Добавьте unknown и stop condition: они сохраняют пробел в знаниях и запрещают незаметно превратить диагностику в rewrite. Если action требует сначала узнать владельца, собрать неизвестный граф зависимостей и изменить код, карточка описывает не задачу, а гипотезу.

Повторяемость тоже имеет границу. Можно считать повтором только тот же diagnostic step, тот же вопрос совместимости или тот же recheck gate. Фразы «мы часто к этому возвращаемся» недостаточно. Если boundary меняется от review к review, события нельзя складывать в один показатель.

Учебная heatmap сопоставляет impact и repeatability, а uncertainty показывает отдельную границу проверки
Учебная heatmap помогает расположить карточки по двум осям. Она задаёт порядок review, но не показывает вероятность отказа и не считает бюджет.

Какие evidence нельзя смешивать

Тип evidence и допустимый вывод
ТипЧто зафиксированоЧего это не доказываетСледующий вопрос
Known artifactЕсть конкретный тест, contract, runbook или запись проверкиЧто artifact покрывает всех потребителей и все путиКакую именно границу он покрывает?
Leading signalДо изменения виден ранний признак роста риска или scopeЧто отказ уже произошёл или обязательно произойдётКакой stop condition сработает раньше?
Lagging signalОдин и тот же вопрос вернулся после reviewЧто известна первопричина или будущая частотаЧто прошлое действие не изменило?
UnknownДанных недостаточно или метод проверки недоступенЧто риск равен нулю и число можно угадатьКакой разрешённый метод изменит статус?

Known artifact сужает область незнания, но не закрывает её автоматически. Leading signal позволяет остановить расширение scope до изменения системы. Lagging signal показывает повторение, но не объясняет причину. Unknown — не пустая клетка. Это явное условие, при котором нельзя усиливать вывод.

Такой подход согласуется с практикой оценки риска: оценка помогает выбрать курс действий, но не заменяет решение владельца. Если неизвестное исчезает при переносе строки в таблицу, формула начинает работать с ложным входом. Сначала сохраните текст неизвестного. Потом решайте, нужен ли отдельный способ его проверить.

Что можно измерять без фальшивой цены

У maintenance item бывает наблюдаемый класс издержки: повторное ручное объяснение, задержка review, дополнительная проверка, возврат к той же границе. Такой label честнее, чем «экономия 14 часов», если команда не зафиксировала период, выборку, метод подсчёта и право использовать эти данные.

Денежная оценка требует отдельного контракта. Нужны scope, период, источник, правило attribution и человек, который принимает допущения. Без них точное число создаёт асимметрию: карточка с выдуманной суммой выигрывает у карточки с честным unknown. Для порядка review достаточно сказать, какой повторяемый шаг мешает работе и как его можно проверить.

Перед расчётом зафиксируйте шкалу. Например, impact и repeatability можно задавать значениями от 1 до 3, uncertainty — от 1 до 3, а evidenceStrength — от 0 до 3. Для каждого значения заранее запишите границу и источник. Если источник отсутствует, оставьте unknown, а не подставляйте ноль.

Учебная формула и воспроизводимый запуск

Ниже — учебный пример. Он работает только с заранее заданными метками impact, repeatability, uncertainty и evidenceStrength. Он не читает production-метрики, не использует историю инцидентов и не предсказывает результат. Сохраните JavaScript в файл priority-demo.mjs, а затем выполните команду из каталога с этим файлом:

node priority-demo.mjs
function teachingPriority(card) {\n  const score = card.impact * card.repeatability\n    + card.uncertainty * 2\n    - card.evidenceStrength;\n\n  const boundary = score >= 10\n    ? 'review-now'\n    : score >= 6\n      ? 'plan-next-review'\n      : 'watch-and-recheck';\n\n  return { score, boundary };\n}\n\n// Учебный объект в памяти. Не production-метрика.\nconst example = teachingPriority({\n  impact: 3,\n  repeatability: 2,\n  uncertainty: 2,\n  evidenceStrength: 1,\n});\nconsole.log(example);\n// { score: 9, boundary: 'plan-next-review' }

Число 9 в этом фрагменте ничего не говорит о вероятности сбоя. Оно только показывает, как выбранные teaching labels переводят карточку в учебную полосу. Если reviewer не согласен с uncertainty или evidenceStrength, спорить нужно с входом и его границей, а не с десятичными знаками.

В реальном проекте такой score можно применять только после явного согласования шкал и источников. Если у карточки появился traffic, incident count или денежная оценка, это не повод молча добавить поле в объект. Нужно пересмотреть модель и правила доступа к данным. Иначе учебная функция начинает изображать систему, которой она не видела.

Симптом → причина → проверка → действие

Диагностика maintenance-приоритета
СимптомПричинаПроверкаДействие
Карточка с высоким score не имеет фактаОценку приняли за evidenceПопросить ссылку на тест, контракт, incident или повторную записьПонизить вывод до unknown и назначить отдельную проверку
Один симптом возвращается на каждом reviewПовторяемая граница не названаСравнить diagnostic step, consumer и recheck gateСформулировать одну bounded проверку, не начинать rewrite
Unknown исчез после расчётаПропуск данных заменили нулёмСверить исходную карточку и обязательные поля моделиОстановить score и сохранить причину неизвестности
Leading signal требует немедленного deployРанний признак перепутали с доказанным отказомПроверить symptom, affected boundary и stop conditionОграничить следующий шаг review или тестом
Lagging signal лечат автоматизациейПовторение приняли за первопричинуОткрыть прошлую карточку и проверить, что изменилосьСначала уточнить owner, evidence и действие, затем выбирать автоматизацию
Денежная сумма определяет очередьНет периода, метода или attributionПроверить источник, выборку, допущения и полномочияВернуть cost к наблюдаемому классу до отдельной оценки

Отрицательный путь важнее красивого score

Проверка должна уметь остановиться. Если карточка не содержит boundary, owner role или evidence, результатом не должен быть score с нулевыми значениями. Ноль означает измеренное отсутствие, а unknown означает отсутствие знания. Эти состояния нельзя смешивать.

Остановите draft, если scope вырос с одной проверки до переписывания подсистемы, если action не имеет stop condition или если неизвестный consumer влияет на решение. Не объявляйте item закрытым после одной удачной проверки. Успешный путь показывает, что выбранный вход обработан. Отрицательный путь показывает, что опасный вход не превратился в разрешение на изменение.

Для lagging signal отдельно сравните прошлое и текущее действие. Если symptom вернулся, спросите, изменился ли contract, owner, evidence или stop condition. Если ничего не изменилось, повторная формулировка задачи не является прогрессом. Если изменилось только название, карточку нужно вернуть в review.

Порядок работы

  1. Опишите symptom. Укажите наблюдаемый факт, место и цену ошибки без предположения о причине.
  2. Назовите boundary. Зафиксируйте consumer, интерфейс, диагностический шаг или recheck gate.
  3. Разделите evidence. Отметьте known artifact, leading signal, lagging signal и unknown отдельно.
  4. Выберите один action. Он должен проверять границу и иметь stop condition без изменения production.
  5. Назначьте повторяемость. Считайте повтором только одинаковый diagnostic step или тот же recheck gate.
  6. Проверьте score. Если применяете учебную шкалу, покажите все входы и не называйте результат вероятностью или ценой.
  7. Передайте decision owner. Он выбирает review-now, plan-next-review или watch-and-recheck с учётом остаточного риска.
  8. Зафиксируйте результат. Запишите, какое знание изменилось, какой путь остановился и что проверять при следующем review.

Ограничения применимости

Heatmap и формула не заменяют risk assessment, change approval, тесты, rollback и наблюдение системы. Они не знают реальный traffic, SLO, support queue, стоимость простоя или число пользователей. Учебный код не подключается к данным и не подтверждает, что выбранные коэффициенты подходят вашему проекту.

Preventive maintenance не всегда нужно автоматизировать. Ручной review может быть обязательным из-за безопасности, прав доступа или редкой операции. Повторяемость сама по себе не доказывает, что автоматизация окупится. Она только помогает найти шаг, который стоит разобрать.

Нельзя выдавать score за вероятность incident. Нельзя считать отсутствие evidence доказательством отсутствия риска. Нельзя включать реальный incident или денежную оценку в учебную модель без нового контракта, метода и ответственного владельца. При такой неопределённости правильное действие — остановить расширение scope.

Проверяемый критерий готовности

Карточка готова к следующему review, если другой инженер может восстановить symptom, boundary, тип сигнала, evidence, неизвестное, один action и stop condition. Для каждого значения понятно, откуда оно взялось. Для каждого перехода между полосами понятна причина. Повторная проверка на том же входе даёт тот же статус.

Если используется score, рядом лежат шкала, формула и явная оговорка о её учебном или локальном статусе. Отрицательная проверка переводит карточку в hold или unknown, а не в нулевой риск. Готовность означает не «задача решена», а «следующий шаг ограничен, проверяем и не маскирует неизвестное».

Проверяемые источники

" } diff --git a/editorial/agent-rewrites/111.json b/editorial/agent-rewrites/111.json index f4733c5..8af9255 100644 --- a/editorial/agent-rewrites/111.json +++ b/editorial/agent-rewrites/111.json @@ -1,7 +1,7 @@ { "index": 111, "slug": "editorial-2024-12-practice-maintenance-retro", - "title": "Maintenance review: как превратить повторяющуюся проблему в проверяемое действие", - "excerpt": "Повторяющаяся ручная работа и старые задачи не образуют план сами по себе. Разбираем симптом, цену ошибки, границу риска и один эксперимент, который можно проверить до большого изменения.", - "contentHtml": "

В конце квартала список сопровождения обычно растёт быстрее, чем команда успевает его читать. В нём соседствуют «обновить зависимость», «разобраться с алертами», «убрать ручной шаг» и «проверить старый контракт». Через месяц эти записи перестают объяснять, что повторяется. Инженер снова выясняет контекст, а затем переносит задачу, потому что не понимает, какой результат считать достаточным.

\n

Симптом виден в работе: один и тот же вопрос возвращается на встречу, оператор вручную повторяет одинаковую последовательность, а изменение обсуждают без владельца и границы проверки. Цена ошибки — не абстрактный технический долг. Команда тратит время на повторное объяснение, принимает решение по неполному контексту и может удалить нужную совместимость раньше, чем найдёт потребителя.

\n

Тезис статьи простой: maintenance review должен превращать жалобу в короткую проверяемую карточку. В ней есть наблюдаемый симптом, риск, видимая цена, принимающая роль, доказательства, неизвестное и один следующий эксперимент. Карточка не разрешает большой рефакторинг. Она помогает решить, что проверить первым и где остановиться.

\n

Сначала зафиксируйте симптом

\n

Начинайте не с названия технологии и не с решения. Запишите действие, которое можно увидеть ещё раз. «Система хрупкая» слишком широко. «При проверке релиза инженер каждый раз вручную ищет, где заканчивается диагностический шаг» уже задаёт границу. Её можно показать в runbook, маршруте, контракте или записи проверки.

\n

Затем укажите цену на уровне, который подтверждён наблюдением. Подойдут «повторный interrupt», «ещё один круг review», «задержка проверки» или «риск удаления потребного пути». Не подставляйте часы, деньги и проценты из ощущения. Точное число требует периода, метода подсчёта и разрешённого источника.

\n

Отделяйте симптом от причины. Повторный ручной шаг может возникнуть из-за отсутствующей инструкции, неясного контракта, неудобного инструмента или неверной границы ответственности. Пока проверка не проведена, причина остаётся гипотезой. Такой порядок не смягчает текст. Он не позволяет спорить о виновнике вместо проверки.

\n

Механизм карточки

\n

Карточка работает как маленький контракт между тем, кто заметил проблему, и тем, кто принимает следующий вопрос. Она не обязана описывать всю систему. Её задача — сузить вопрос до одного эксперимента и сохранить то, чего мы пока не знаем.

\n
Поля maintenance review: симптом → причина → проверка → действие
ПолеЧто записатьПроверкаЧего не утверждать
СимптомПовторяемое действие и его границаДругой инженер может указать тот же шаг или артефакт«Так происходит везде» без проверенного охвата
ПричинаГипотеза с опорой на конкретный артефактЕсть лог, тест, контракт, diff или запись наблюдения«Плохой код» без доказательства
ЦенаКласс усилия или риск пересечения границыПонятно, что повторится при бездействииПридуманная экономия и точный прогноз потерь
ВладелецРоль, принимающая следующий узкий вопросУ роли есть полномочие принять или отклонить действиеИмя человека без согласия и полномочий
ДоказательстваИзвестные факты и список неизвестногоДля каждого факта указан источник или способ проверкиПолноту, которой проверка не показала
ДействиеОдин эксперимент и критерий остановкиРезультат изменит знание, а не только создаст активностьАвтоматическое разрешение deploy, удаления или rewrite
\n

Важна именно связка полей. Симптом без цены превращается в раздражитель. Цена без причины превращается в приоритет «на глаз». Причина без проверки создаёт спор. Проверка без действия оставляет запись в том же состоянии. Карточка готова к review, когда следующий шаг ограничен и его результат можно увидеть.

\n

Пример: повторный ручной шаг перед релизом

\n

Ниже учебный пример. Он не описывает реальный сервис, команду или измеренный результат. Представим, что перед каждым релизом инженер вручную сравнивает список маршрутов с короткой инструкцией. Инструкция не говорит, на каком условии проверку можно закончить. Ошибка в карточке была бы такой: «автоматизировать релиз». Это уже решение, а не описание проблемы.

\n

Рабочая карточка выглядит уже: симптом — повторное ручное сравнение маршрутов; риск — изменение может пройти без проверки одного compatibility boundary; цена — ещё один review pass и interrupt; владелец — роль, отвечающая за release checklist; известное — в инструкции нет stop condition; неизвестное — какие потребители используют старый маршрут; эксперимент — добавить один явный stop condition и прогнать его на фиксированном учебном наборе маршрутов.

\n
const reviewItem = {\n  symptom: 'manual route comparison repeats before each review',\n  risk: 'a compatibility boundary may be skipped',\n  costClass: 'repeat-review',\n  ownerRole: 'release-checklist owner',\n  evidence: ['runbook step 4 has no stop condition'],\n  unknown: ['consumers of the legacy route'],\n  nextExperiment: 'add one stop condition and check a fixed route set',\n};\n\nconst canContinue =\n  reviewItem.evidence.length > 0 &&\n  reviewItem.nextExperiment.length > 0 &&\n  reviewItem.unknown.length > 0;\n\nconsole.log(canContinue); // учебный результат: true
\n

Код показывает только форму данных и условие перехода к review. Он не читает репозиторий, сеть, метрики, логи или состояние сервиса. Значение true означает лишь, что учебная карточка заполнена минимально. Оно не доказывает наличие потребителей, безопасность изменения и экономию времени.

\n

Как читать симптом и выбирать действие

\n
Диагностическая таблица
СимптомВероятная причинаПроверкаДействие
Один вопрос возвращается на каждом reviewНе задана граница завершенияНайти шаг инструкции и попросить коллегу назвать stop conditionСформулировать одну границу и повторить проверку
Ручной шаг повторяется и растёт вместе с числом объектовПроцесс не имеет устойчивого автоматизированного путиРазделить обязательную проверку и повторяемую механикуПроверить малый bounded experiment, не автоматизировать всё сразу
Удаление старого пути выглядит безопаснымНе проверены потребители или совместимостьПроверить контракт, ссылки и restore-вопросОстановить removal и назначить recheck
Есть риск, но нет принимающей ролиКарточка описывает проблему, а не ответственностьНазвать роль с правом принять residual riskСначала задать owner question, потом расширять scope
В карточке появился точный scoreНеизвестное заменили удобным числомРазложить score на входы и источникиВернуть класс цены и отдельно записать unknown
\n

Эта таблица не заменяет диагностику. Она задаёт порядок вопросов. Если симптом не совпадает ни с одной строкой, не подгоняйте его под знакомый шаблон. Добавьте наблюдение, уточните границу и только затем решайте, нужен ли новый тип проверки.

\n

Иллюстрация цикла

\n
\"Цикл
Цикл ограничивает scope: наблюдение ведёт к одному эксперименту, а не к автоматическому изменению системы.
\n

Схема важна из-за последней развилки. Если эксперимент добавляет redesign, удаление или обещание неизвестных данных, карточка останавливается. Это отрицательный путь, а не неудача. Он показывает, что текущая граница слишком мала для предлагаемого действия. Сохраните исходный симптом и откройте отдельный review с новым scope.

\n

Порядок действий

\n
  1. Запишите один симптом. Укажите повторяемый шаг, маршрут, контракт или вопрос. Уберите слова «всё», «всегда» и «система» без границы.
  2. Назовите риск. Опишите, какую границу можно пересечь и какое решение станет ошибочным.
  3. Укажите цену классом. Запишите повторный interrupt, задержку review, ручное усилие или риск несовместимости. Числа добавляйте только с методом и источником.
  4. Назначьте роль. Найдите того, кто может принять следующий вопрос или вернуть его на уточнение.
  5. Разделите known и unknown. Для каждого факта укажите артефакт. Не превращайте отсутствие данных в нулевой риск.
  6. Сформулируйте один эксперимент. Он должен изменить знание и иметь stop condition. Не называйте экспериментом deploy, удаление или большой рефакторинг.
  7. Проведите recheck. Сравните результат с исходным симптомом. Если повторение не исчезло или граница стала шире, пересмотрите карточку.
\n

Что считать поддержанием, а что — рутинной нагрузкой

\n

Не вся ручная работа является дефектом. Иногда человек обязан принять решение, проверить исключение или подтвердить риск. Google SRE отличает toil от полезной инженерной работы по признакам: работа ручная, повторяемая, предсказуемая, без устойчивой ценности и растёт вместе с системой. Это полезная проверка гипотезы, но не универсальный повод для автоматизации.

\n

Если шаг требует экспертного решения, автоматизируйте подготовку данных, а не само решение. Если шаг повторяет одну и ту же механику и не меняет вывод, ищите маленький эксперимент. Если ручная проверка существует ради безопасности, её удаление может увеличить риск. В карточке нужно записать, что именно должно остаться человеческим.

\n

Ограничения

\n

Maintenance review не выдаёт вероятность инцидента и не вычисляет бюджет исправления. Учебная карточка не знает реальный traffic, список потребителей, окно изменений, требования отката и полномочия ролей. Пример с маршрутами фиксирует только форму рассуждения. Его нельзя переносить в рабочую систему без отдельной проверки входов и разрешения на изменение.

\n

Ограничение scope защищает от двух ошибок. Первая — начать большую переделку по одному повторному вопросу. Вторая — удалить старый путь, потому что в известном наборе ссылок его не нашли. В обоих случаях неизвестное ошибочно приняли за отсутствие зависимости. Отрицательный результат проверки означает «в этом методе и охвате не найдено», а не «этого нет».

\n

Критерий готовности

\n

Maintenance review готов к следующему решению, если независимый инженер может за несколько минут ответить на пять вопросов: какой симптом повторяется; какую границу он затрагивает; что уже доказано; что остаётся неизвестным; какой один эксперимент и stop condition идут дальше. После эксперимента есть повторная проверка, связанная с тем же симптомом. Если хотя бы один ответ требует устного контекста автора, карточка ещё не готова.

\n

Проверяемые источники

\n" + "title": "Maintenance review: как превратить повторяющуюся проблему в проверяемый план", + "excerpt": "Повторяющиеся задачи сопровождения нельзя приоритизировать по раздражению. Разбираем, как отделить симптом от причины, оценить риск, проверить гипотезу на выгрузке и оставить команде ограниченный следующий шаг.", + "contentHtml": "

Список сопровождения редко ломается одним большим инцидентом. Он постепенно заполняется похожими пунктами: вручную проверить релиз, снова обновить уязвимую библиотеку, найти владельца старого маршрута, повторить сверку после сбоя. Через несколько недель записи смешивают симптом, причину и желаемое решение. На встрече команда спорит о приоритете, но не может ответить, что именно проверять и когда остановиться.

\n

Цена такой путаницы измеряется не только временем встречи. Ошибка в maintenance review может оставить известный риск без владельца или, наоборот, привести к удалению совместимости по неполному поиску потребителей. Рабочий выход — превратить каждую повторяющуюся проблему в короткую карточку: наблюдение, граница, доказательство, неизвестное, риск и один проверяемый следующий шаг.

\n

Ниже — рабочая схема для инженерной команды. Она не заменяет incident review, threat modeling, change approval или требования к эксплуатации. Её задача уже: помочь решить, является ли запись рутинной нагрузкой, профилактическим улучшением или отдельным исследованием.

\n

Сначала назовите повторяемое действие

\n

Начинайте с наблюдения, которое другой человек сможет найти в том же артефакте. «Код устарел» не задаёт проверку. «Перед каждым релизом инженер вручную сравнивает список маршрутов с инструкцией, а в инструкции нет условия завершения» задаёт и действие, и место поиска.

\n

Запишите контекст: период, сервис или компонент, инициатора, вход и результат. Если факт взят из тикетов, лога, runbook или истории изменений, сохраните ссылку на этот артефакт. Не называйте причиной то, что пока является только предположением. Отсутствие найденной записи также не доказывает отсутствия зависимости.

\n
Минимальная карточка maintenance review
ПолеЧто фиксироватьКак проверитьГраница вывода
СимптомПовторяемое действие и его охватПовторить поиск по датам, компонентам или операциямНе обобщать на весь продукт по одной записи
ДоказательствоЛог, тикет, diff, тест, контракт или измерениеДругой инженер открывает тот же источникОтделять факт от пересказа
РискЧто может быть пропущено, повреждено или удаленоОписать затронутую границу и негативный путьНе выдавать класс риска за вероятность инцидента
ЦенаПовторный interrupt, ручное усилие или задержка проверкиУказать период и способ подсчёта, если есть числоНе придумывать экономию без исходных данных
НеизвестноеПотребители, права, версия, откат или условие средыНазначить отдельный способ узнать значение«Не найдено» означает ограниченный охват поиска
Следующий шагОдин эксперимент и критерий остановкиРезультат должен изменить решениеНе подменять эксперимент deploy или удалением
\n

Поля связаны последовательно. Симптом без доказательства остаётся впечатлением. Доказательство без границы создаёт ложную полноту. Риск без неизвестного заставляет считать пробел нулевым риском. Следующий шаг без stop condition превращается в большой рефакторинг, который начался с маленькой жалобы.

\n

Разделите toil, профилактику и инженерное изменение

\n

В Google SRE toil — это операционная работа, которая обычно ручная, повторяемая, автоматизируемая, реактивная, не оставляет устойчивого улучшения и растёт вместе с масштабом сервиса. Эти признаки помогают проверить гипотезу, но не образуют обязательную классификацию для любой команды. Ручная проверка безопасности может быть оправданной, а разовая работа с legacy-кодом может оставить постоянное улучшение.

\n

Для maintenance review полезно спросить, что остаётся в системе после выполнения шага. Если оператор каждый раз выполняет одну механику, состояние сервиса не меняется, а число объектов увеличивает ручное усилие, перед нами кандидат на устранение toil. Если результатом становится обновлённый мониторинг, документированный контракт или исправленный процесс, это уже инженерное улучшение, даже если до него пришлось выполнить неприятную ручную работу.

\n

Профилактическое сопровождение имеет отдельную границу. NIST SP 800-40 Rev. 4 описывает patch management как процесс выявления, приоритизации, получения, установки и проверки обновлений. Этот цикл применим к обновлениям и уязвимостям. Его нельзя механически использовать как доказательство, что любой старый тикет нужно закрыть патчем.

\n
\"Схема
Узкая карточка удерживает порядок рассуждения: сначала наблюдение и доказательства, затем ограниченный эксперимент и повторная проверка.
\n

Проверьте гипотезу на выгрузке

\n

Учебный пример ниже работает с локальным TSV-файлом. В нём четыре столбца: дата, область, симптом и минуты ручного участия. Команда может экспортировать такие строки из трекера или журнала, но способ экспорта и полнота данных зависят от конкретной системы. Команда не меняет исходный файл: awk только читает его и группирует одинаковые пары «область + симптом».

\n
# maintenance.tsv, первая строка — заголовок\n# date\\tarea\\tsymptom\\toperator_minutes\n2024-10-07\\trelease\\tmanual route comparison\\t25\n2024-10-21\\trelease\\tmanual route comparison\\t20\n2024-11-04\\tdependency\\tcheck vulnerable package\\t15\n2024-11-18\\trelease\\tmanual route comparison\\t30\n\nawk -F '\\t' '\n  NR == 1 { next }\n  { key = $2 SUBSEP $3; runs[key]++; minutes[key] += $4 }\n  END {\n    print \"area\\tsymptom\\truns\\toperator_minutes\"\n    for (key in runs) {\n      split(key, part, SUBSEP)\n      print part[1] \"\\t\" part[2] \"\\t\" runs[key] \"\\t\" minutes[key]\n    }\n  }\n' maintenance.tsv | sort -t '\\t' -k3,3nr
\n

Ожидаемый вывод содержит три запуска и 75 минут ручного участия для ручной сверки маршрутов, а также один запуск и 15 минут для проверки пакета. Это не прогноз экономии и не оценка риска: в учебной выгрузке нет стоимости простоя, полноты выборки, сложности операции и сведений о последствиях. Вывод лишь помогает выбрать первую запись для проверки повторяемости.

\n

Перед использованием команды проверьте формат времени и разделителя. Если симптом пишется разными словами, группировка разделит одну проблему на несколько строк. Нормализация текста должна быть отдельным осознанным шагом: автоматическое склеивание похожих формулировок может объединить разные риски.

\n

Из числа не следует приоритет

\n

75 минут ручного участия выглядят убедительно, но сами по себе не говорят, что эту работу нужно автоматизировать первой. Маленький по времени шаг может затрагивать платёжный контракт, секрет или восстановление данных. Большая сумма минут может приходиться на безопасную контрольную процедуру, которую нельзя убирать без компенсирующей защиты.

\n

Оценку удобно вести в двух независимых измерениях. Первое — повторяемость: сколько запусков, какой период и насколько растёт усилие. Второе — последствие ошибки: какой объект затрагивается, есть ли совместимость, доступность отката и способ обнаружить неверный результат. Их пересечение выбирает проверку, но не выдаёт готовый балл. NIST SP 800-30 Rev. 1 прямо связывает оценку риска с информацией, необходимой для выбора мер реагирования; это рамка принятия решения, а не формула для расчёта риска из четырёх столбцов TSV.

\n
Как выбрать первый вопрос для проверки
НаблюдениеПервый вопросБезопасное действиеКогда остановиться
Много повторов, низкое последствие ошибкиМожно ли убрать механику без изменения решения?Сделать dry run на копии входа и сравнить результатРезультат зависит от неописанного ручного суждения
Мало повторов, высокое последствиеКак доказать охват потребителей и откат?Составить карту зависимостей и негативный тестНе известны владелец, версия или путь восстановления
Много повторов, высокое последствиеКак снизить ручную нагрузку, сохранив контроль?Автоматизировать подготовку и оставить approval на границеАвтоматический шаг не имеет аудита или stop condition
Данных недостаточноКакой минимальный сбор подтвердит охват?Добавить наблюдение на ограниченный периодСбор сам меняет критичный путь или раскрывает секреты
\n

Соберите проверку до изменения

\n

Безопасный эксперимент отвечает на один вопрос. Для ручной сверки маршрутов это может быть сравнение двух списков на фиксированном наборе входов с сохранением расхождений. Для обновления библиотеки — проверка версии, затронутых потребителей, тестов и процедуры возврата. Для старого endpoint — поиск вызовов, проверка телеметрии, подтверждение владельца и тест отрицательного сценария.

\n

У эксперимента должны быть вход, команда или процедура, ожидаемый результат и stop condition. «Сделать dry run и посмотреть» недостаточно: запишите, что считается совпадением, какое расхождение требует остановки и где лежит результат. Если проверка не может отличить две гипотезы, она создаёт активность, но не знание.

\n

Сначала проверяйте на копии, тестовом проекте или чтении, если это соответствует архитектуре и требованиям доступа. Не переносите команды из примера в production без проверки прав, версии инструмента, формата данных, лимитов и процедуры отката. Особенно опасны операции, которые удаляют старые версии, массово меняют конфигурацию или отправляют данные во внешнюю систему.

\n

Порядок внедрения

\n
  1. Соберите наблюдения. Возьмите ограниченный период и один тип сопровождения. Для каждой строки сохраните дату, компонент, действие и ссылку на исходный артефакт.
  2. Нормализуйте только явно. Объединяйте формулировки симптомов по правилу, которое можно прочитать и отменить. Не скрывайте разные контракты под одним ярлыком.
  3. Заполните карточку. Отделите факт от гипотезы, назовите неизвестное и опишите границу последствий.
  4. Выберите один эксперимент. Он должен быть обратимым или read-only, иметь фиксированный вход, ожидаемый результат и условие остановки.
  5. Сравните с исходным симптомом. После проверки спросите, уменьшилась ли повторяемость, изменилась ли обнаруживаемость ошибки и не появился ли новый путь отказа.
  6. Примите отдельное решение. Устранить механику, оставить контроль, открыть исследование или закрыть запись как единичную. Не называйте тикет решённым только потому, что эксперимент завершился.
\n

Ограничения применимости

\n

Этот подход не вычисляет вероятность инцидента, стоимость простоя, технический долг или необходимый штат. Для таких выводов нужны данные конкретного сервиса и согласованные правила оценки. TSV-пример годится для локальной иллюстрации группировки; он не доказывает полноту журнала и не заменяет систему аудита.

\n

Google SRE описывает toil в контексте эксплуатации production-сервисов. NIST SP 800-40 посвящён корпоративному patch management, а SP 800-30 — руководству по оценке рисков федеральных информационных систем и организаций. Их определения и процессы полезны как проверяемые рамки, но команда должна адаптировать их под свои роли, договорённости, регуляторные требования и класс данных.

\n

Не автоматизируйте решение, если человек обязан подтвердить юридическое условие, безопасность, бизнес-ограничение или восстановление. В таком случае автоматизируйте сбор входов, сравнение и подготовку отчёта, а точку принятия решения оставьте явной и журналируемой.

\n

Критерий готовой карточки

\n

Карточка готова к review, когда независимый инженер без устного контекста может ответить на пять вопросов: какой симптом повторяется; где его граница; каким источником подтверждён факт; что ещё неизвестно; какой один шаг и какой результат определят решение. После эксперимента есть ссылка на результат и повторная проверка исходного симптома.

\n

Если ответом остаётся «надо сначала разобраться со всем сервисом», scope слишком широк. Сузьте компонент, период и вопрос либо откройте отдельное исследование. Maintenance review приносит пользу не тогда, когда превращает каждую запись в автоматизацию, а когда делает следующий выбор проверяемым и оставляет команде понятную границу ответственности.

\n

Проверяемые источники

\n" } diff --git a/editorial/agent-rewrites/112.json b/editorial/agent-rewrites/112.json index 40f84f4..38e3aca 100644 --- a/editorial/agent-rewrites/112.json +++ b/editorial/agent-rewrites/112.json @@ -3,5 +3,5 @@ "slug": "editorial-2024-11-field-deprecation", "title": "Как удалить устаревший API и не сломать последнего клиента", "excerpt": "Депрекация не доказывает, что endpoint больше не используют. Разбираем removal gate: как отделить активного клиента от неизвестной зоны, выбрать проверку, остановить удаление и определить готовность изменения.", - "contentHtml": "

После миграции старого endpoint команда видит зелёные тесты, пустой список известных клиентов и открывает удаление. В следующем релизе один интегратор получает 404 или 410. Его не нашли, потому что поиск прошёл только по репозиторию, а клиент жил в другом аккаунте, в старой версии SDK или за пределами выбранных логов. Цена ошибки — не только один сбой. Команда теряет совместимость, получает срочный откат и уже не может точно сказать, какую область проверила.

\n

Проблема начинается с неверного вопроса: «кто последний consumer?». Полный список потребителей часто недостижим. Рабочий вопрос уже: «какие условия допускают удаление этого ресурса, что осталось неизвестным и какое наблюдение остановит change?». Это removal gate — отдельная проверка перед удалением. Она не обещает отсутствие скрытых клиентов. Она делает риск ограниченным, видимым и управляемым.

\n

Что именно устаревает

\n

Сначала зафиксируйте один ресурс. Это может быть GET /v1/orders/{id}, операция с конкретным operationId или поле ответа в версии контракта. Не называйте предметом проверки «старый API» целиком. У разных маршрутов будут разные владельцы, клиенты и сроки.

\n

Депрекация меняет статус ресурса, но не должна незаметно менять его поведение. В OpenAPI поле deprecated: true сообщает о статусе операции. HTTP-заголовок Deprecation сообщает тот же сигнал во время запроса. Ссылка через Link может вести к описанию причины и замены. Ни один из этих сигналов не доказывает, что клиент прочитал уведомление и перешёл на новый маршрут.

\n

Sunset тоже не является доказательством. Он обозначает ожидаемую границу, после которой ресурс может стать недоступным. Это дата для миграционного плана, а не подтверждение, что все callers уже ушли. Между уведомлением и удалением нужен отдельный decision.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
Все найденные клиенты migratedСписок известных строк приняли за полную populationНазвать scope источника, период и blind zoneОставить unknown и заблокировать автоматическое удаление
Трафик равен нулюПроверка не видит нужный регион, credential или кэшСверить охват telemetry с ресурсом и клиентамиРасширить разрешённую проверку или сохранить совместимость
Есть Deprecation и дата SunsetСигнал перепутали с фактом миграцииПроверить replacement, доставку notice и статус каждого клиентаПродолжить миграцию; removal gate не закрывать
Нашёлся active consumerВладелец начал change до согласования последнего клиентаПроверить owner, контракт и путь переходаОстановить удаление и вернуть задачу на миграцию
Неясно, как откатить changeRestore boundary не описали до удаленияНазвать последний совместимый контракт и stop conditionНе начинать removal change
\n

Механизм removal gate

\n

Разделите результат на три состояния. Active означает, что проверка нашла действующий вызов или зависимость. Unknown означает, что область не наблюдается или её нельзя проверить в разрешённом scope. Migrated означает, что названная зависимость перешла на replacement. Эти слова описывают разные факты. Нельзя превратить unknown в migrated только потому, что известные строки уже закрыты.

\n

Active сразу блокирует удаление. У него должен быть владелец, способ связаться с ним и новый контракт. Unknown тоже блокирует автоматическое удаление, но по другой причине: неизвестность не равна нулевой активности. Для неё нужен владелец остаточного риска и конкретное решение — расширить проверку, продлить поддержку или принять ограниченный риск на human review. Migrated допускает подготовку предложения, но не означает, что маршрут можно удалить без отдельного change.

\n

Каждая строка evidence должна отвечать на пять вопросов: какой ресурс проверяли, каким инструментом, за какой период, в какой области и чего инструмент не видит. Запись «usage = 0» без этих полей слаба. Она выглядит точной, но не объясняет, что именно измерено.

\n
\"Схема
Removal gate разделяет active, unknown и migrated. Схема учебная: она показывает порядок решения, но не обнаруживает реальных клиентов.
\n

Учебный пример контракта

\n

Ниже приведён ограниченный учебный пример. Он не читает access log, код, сеть, CI или production и не возвращает реальные данные. Его задача — показать форму записи, в которой неизвестная зона остаётся явной.

\n
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};
\n

В этом примере первый consumer migrated, но второй остаётся unknown. Поэтому итог — block-removal. Поле blindZone не украшает отчёт. Оно показывает, почему у команды нет права назвать результат полным. Если для unknown нельзя назвать следующую разрешённую проверку, риск нужно принять явно или сохранить старый контракт.

\n

Порядок действий

\n
  1. Определите границу. Запишите метод, URI или поле, версию, operationId, replacement и владельца. Уберите из формулировки соседние операции.
  2. Остановите новые зависимости. Обновите документацию и контракт. Добавьте понятную ссылку на замену. Если применяете Deprecation, проверьте scope заголовка. Notice не должен менять semantics ответа.
  3. Назначьте срок как boundary. При необходимости объявите Sunset и объясните, что это ожидаемая дата возможной недоступности. Не выдавайте её за гарантию миграции.
  4. Соберите evidence по типам. Разделяйте исходный код, зависимости, runtime-наблюдение, authorization scope и неизвестные области. Для каждой строки храните инструмент, период, охват и blind zone.
  5. Разнесите клиентов по состояниям. Active блокирует. Unknown блокирует автоматическое удаление. Migrated переводит вопрос на human review, но не закрывает его сам.
  6. Сформулируйте stop condition. Например: «проверка нашла active row» или «owner replacement не подтвердил совместимость». При таком факте review прекращается.
  7. Опишите restore boundary. Назовите последний совместимый контракт, способ вернуть маршрут и ограничения отката. Если изменение уже записывает необратимые данные, возврат HTTP-маршрута не решает проблему.
  8. Проведите отдельный removal review. Удаление должно быть самостоятельным изменением с понятным владельцем, residual risk и планом проверки после релиза.
\n

Отрицательный путь важнее зелёного статуса

\n

Хороший gate часто заканчивается отказом. Это не ошибка процесса. Если есть active row, команда получает конкретную работу по миграции. Если есть unknown, команда не маскирует пробел красивым нулём. Если replacement меняет поля, коды ошибок или порядок авторизации, старый маршрут остаётся до согласования совместимости.

\n

Опасный путь выглядит иначе: поиск вернул пусто, в отчёте написали «клиентов нет», дату sunset приняли за дедлайн, а удаление объединили с миграцией. Такой результат нельзя воспроизвести и нельзя честно откатить. Пустой результат — это только утверждение инструмента в его границах.

\n

Ограничения

\n

Ни один источник не даёт универсального способа доказать отсутствие всех consumers. Логи могут не охватить редкий вызов. Dependency inventory не видит динамически собранный URL. Внутренний сервис может ходить через общий gateway. Credential scope может скрывать другой tenant. Кэш и очередь могут отложить вызов за пределы выбранного периода. Поэтому removal gate должен хранить границу наблюдения, а не только вердикт.

\n

Учебная таблица и код выше не являются telemetry, списком клиентов, результатом incident analysis или production evidence. Их можно использовать как шаблон полей. Реальные значения нужно получать из разрешённых систем и проверять у владельцев этих систем. Если доступ к источнику отсутствует, состояние остаётся unknown.

\n

Проверяемый критерий готовности

\n

Удаление готово к отдельному change только тогда, когда одновременно выполнены пять условий: ресурс и replacement однозначно определены; notice и его scope опубликованы; каждая известная зависимость имеет состояние и владельца; unknown-зона записана с методом, периодом и stop condition; restore boundary проверена на совместимом контракте. Финальный review должен ответить «да» или «нет» на каждый пункт.

\n

После удаления проверьте не только код ответа. Проверьте, что новый маршрут принимает прежние обязательные сценарии, что старый маршрут действительно недоступен в заявленной области и что ошибки не появились у клиентов, которых охватил change. Если хотя бы один критерий не проверен, удаление не закончено — оно только запланировано.

\n

Проверяемые источники

\n" + "contentHtml": "

Команда пометила GET /v1/orders/{id} устаревшим, выпустила замену и увидела ноль вызовов в выбранном графике. Удаление кажется безопасным, пока внешний интегратор не получает 404 или 410. Такой сбой начинается не с плохого HTTP-кода, а с неверного вывода: отсутствие записей в одном источнике приняли за отсутствие всех потребителей.

\n

Удаление API нужно рассматривать как доказательство с ограниченной областью, а не как дату в календаре. Сначала зафиксируйте одну операцию, затем свяжите известные вызовы с владельцами, явно запишите слепые зоны и только после этого откройте отдельный removal review. Если остался активный или неизвестный потребитель, автоматическое удаление должно остановиться.

\n

Депрекация не равна удалению

\n

В OpenAPI 3.1.0 поле deprecated: true объявляет операцию устаревшей и рекомендует потребителям перейти с неё. Это часть описания контракта. Поле не читает access log, не знает о старых версиях SDK и не подтверждает, что replacement поддерживает тот же сценарий. Если генератор документации не публикует это поле или использует другую версию OpenAPI, поведение нужно проверить в конкретном toolchain.

\n

Заголовок Sunset решает другую задачу. RFC 8594 описывает его как сигнал о том, что URI, вероятно, станет недоступен в указанный момент. Документ прямо называет timestamp подсказкой: после этой даты возможны ошибки, редирект или полная недоступность, но точное поведение не обещано. Поэтому Sunset помогает потребителю спланировать миграцию, но не заменяет inventory и проверку владельца.

\n

После удаления ответы тоже нужно трактовать аккуратно. RFC 9110 описывает 404 как отсутствие текущего представления или нежелание раскрывать его существование. 410 означает, что доступ больше недоступен и состояние, вероятно, постоянно. Если сервер не знает, что удаление окончательно, RFC рекомендует 404. Ни один статус сам по себе не доказывает, что все клиенты перешли на новый маршрут.

\n

Сначала сузьте предмет проверки

\n

Фраза «удаляем v1» слишком широкая для решения. В одной версии могут жить разные методы, форматы ошибок и права доступа. Для removal gate запишите method, URI template, operationId, версию схемы, replacement, владельца и дату, после которой разрешён отдельный change. Если меняется только поле ответа, проверяйте поле, а не весь маршрут.

\n

Сравните не только URL. Для каждого обязательного сценария зафиксируйте authentication boundary, коды ошибок, pagination, идемпотентность, лимиты и правила повторной отправки. Клиент может успешно получить 200 от нового маршрута и всё равно сломаться, если новый контракт иначе трактует пустой результат или отказ в доступе.

\n
Быстрый разбор перед удалением одной операции
НаблюдениеЧто оно доказываетЧего не доказываетСледующее действие
deprecated: true в OpenAPIОперация объявлена устаревшейПотребители мигрировалиОпубликовать replacement и начать consumer map
В графике нет запросовВ выбранном scope вызовы не наблюдалисьВызовов нет вообщеЗаписать период, sampling, регион и credential scope
Все найденные rows migratedНазванные потребители имеют путь переходаСписок потребителей полныйПроверить unknown-зону и назначить остаточный риск
Новая операция отвечает 200Один проверенный запрос прошёлСовместимы ошибки, права и редкие сценарииСравнить фиксированный набор contract cases
Назначена дата SunsetЕсть объявленная граница планированияРесурс станет недоступен точно в эту датуСогласовать отдельный removal change и stop condition
\n

Consumer map хранит найденное и неизвестное

\n

Строка consumer map должна связывать потребителя не с догадкой, а с evidence. Минимальный набор полей: имя потребителя, состояние, источник, период, охват, владелец, replacement и следующий шаг. Состояние active означает наблюдаемый вызов старого контракта. migrated означает, что названный потребитель перешёл и это подтверждено проверкой. unknown означает, что область не наблюдается или не проверена.

\n

Unknown — не мягкая форма migrated. Если telemetry не включает внешний tenant, старые credentials, редкий cron или трафик через общий gateway, строка должна остаться неизвестной. Запись «usage = 0» без периода и границы выглядит точной, но не позволяет воспроизвести вывод. За unknown назначают владельца остаточного риска: он либо расширяет разрешённую проверку, либо оставляет совместимость, либо выносит принятие риска на явное human review.

\n
\"Схема
Removal gate разделяет активный, неизвестный и мигрированный потребитель. Иллюстрация показывает логику решения, но не является телеметрией конкретного API.
\n

Правило removal gate

\n

Разделите решение на три ветки. Любой active блокирует удаление: сначала нужен владелец и согласованный путь миграции. Любой unknown блокирует автоматическое удаление: неизвестность не равна нулевой активности. Если все названные строки migrated, можно открыть ручной review, но это ещё не разрешение удалить маршрут. В review должны попасть контракт, owner, notice, residual risk и план проверки после релиза.

\n

Простейшее правило можно повторить локально на фиксированном наборе состояний. Команда ниже ничего не читает из production и не делает сетевых запросов: она только показывает, что unknown должен привести к блокировке.

\n
node --input-type=module -e \"const evidence = [{ state: 'migrated' }, { state: 'unknown' }]; const blocked = evidence.some(function (row) { return row.state === 'active' || row.state === 'unknown'; }); console.log(blocked ? 'block-removal' : 'human-review');\"\n\n// Ожидаемый вывод:\n// block-removal\n
\n

В учебной записи один известный сервис мигрировал, но внешняя зона не проверена. Поэтому автоматический результат — block-removal. Поле blindZone объясняет причину отказа. В реальном проекте замените фиктивные rows на записи из разрешённых источников и сохраните ссылку на запрос, dashboard или commit, который можно открыть повторно.

\n

Проверка по слоям

\n
  1. Контракт. Найдите операцию по operationId и URI, затем проверьте request, response, ошибки, auth и replacement. Для локального репозитория начните с команды rg -n 'getOrderV1|/v1/orders' .. Поиск показывает строки, но не доказывает runtime-вызов.
  2. Зависимости. Проверьте исходники, lock-файлы, сгенерированные клиенты, документацию и конфигурацию gateway. Отдельно ищите динамически собранные URL и старые версии пакетов.
  3. Наблюдаемость. Возьмите запросы за заранее выбранный период и запишите route, client identity, регион, tenant, sampling, кеши и очереди. SQL-шаблон SELECT client_id, count(*) FROM api_access WHERE route = '/v1/orders/{id}' AND ts >= :from AND ts < :to GROUP BY client_id; нужно адаптировать к своей схеме; результат без описания охвата остаётся частичным.
  4. Владельцы. Для каждой найденной строки назначьте человека или команду, срок миграции, replacement и способ связаться. Внешнего потребителя нельзя считать migrated по тому, что внутренний сервис собрался.
  5. Совместимость. Прогоните одинаковый фиксированный набор сценариев против v1 и v2 в тестовой среде. Сравните обязательные поля, коды ошибок, права, pagination, ретраи и побочные эффекты. Один успешный happy path не закрывает контракт.
  6. Уведомление. Обновите OpenAPI, migration guide и changelog. Если используете Sunset, явно укажите scope и трактуйте дату как сигнал, а не гарантию доступности или удаления.
  7. Stop condition. Заранее запишите факты, при которых review прекращается: active row, unresolved unknown, несовместимая ошибка, неподтверждённый owner или отсутствие способа восстановить старый контракт.
  8. Отдельное изменение. Удаление оформите самостоятельным change с наблюдением после релиза. Не прячьте его внутри миграции, чтобы зелёные тесты нового клиента не замаскировали старого.
\n

Restore boundary важнее обещания отката

\n

До удаления назовите последний совместимый контракт и способ временно вернуть обработчик. Если новый код уже изменяет данные, восстановление HTTP-маршрута не вернёт прежнее состояние. Тогда граница восстановления должна включать совместимый read path, миграцию данных или заранее подготовленный обратный план.

\n

Согласуйте, кто может остановить rollout и какой сигнал это делает. Например, рост ответов 4xx от известного клиента после удаления — достаточное основание остановить change, но не доказательство, что причина именно в API. Нужны correlation id, журнал изменения и сравнение с baseline. Откат должен вернуть систему в проверяемое состояние, а не просто скрыть симптом.

\n

После удаления проверяется не только статус

\n

Проверка после релиза должна охватить старый и новый путь в заявленной области. Для старого маршрута проверьте ожидаемый статус и тело ошибки тестовым клиентом, не используя реальные секреты. Для нового — повторите контрактные сценарии и убедитесь, что авторизация, лимиты и ответы соответствуют согласованному replacement.

\n

Затем посмотрите на реальные сигналы: ошибки по client identity, retry rate, latency, gateway responses, очереди и обращения владельцев. Учитывайте задержку доставки логов и кэш. Если removal change коснулся только одного региона или credential scope, честный итог звучит так: «в заявленной области старый путь недоступен». Формулировка «клиентов не осталось» требует более сильного доказательства, чем обычно может дать один график.

\n

Ограничения применимости

\n

Полного доказательства отсутствия всех consumers обычно не существует. Редкий cron не попадёт в короткое окно. Динамический URL может исчезнуть из статического поиска. Общий gateway скроет исходного клиента. Другой tenant, регион, credential или sampling оставит blind zone. Кэш, retry и очередь сдвинут вызов за пределы момента проверки.

\n

Поэтому removal gate — это контроль риска, а не математическое доказательство. Он работает, когда у команды есть право читать нужные источники и назначать владельцев. При отсутствии доступа состояние остаётся unknown. Учебные rows и команда выше не являются фактическими данными, incident report или результатом запроса к вашему API.

\n

Критерий готовности

\n

Операция готова к отдельному removal change, если одновременно выполнены пять условий: scope операции и replacement однозначны; OpenAPI и уведомление опубликованы; каждая известная зависимость имеет owner и состояние; неизвестные области перечислены с периодом и stop condition; restore boundary проверена на совместимом контракте. Для каждого пункта оставьте ссылку на evidence.

\n

Если хотя бы один пункт не выполнен, решение не должно превращаться в «удалим и посмотрим». Оставьте старую операцию, ограничьте новые зависимости и назначьте следующий проверяемый шаг. Депрекация тогда становится управляемым переходом, а не бессрочной пометкой, которая однажды внезапно превращается в 404.

\n

Проверяемые источники

\n" }