DarkRiDDeR16 мин

Учебный разбор инцидента: почему временный обход не становится выводом

НадёжностьОтладкаПрактика

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

Ниже не рассказ о существующей системе. Это детерминированный сценарий в памяти Node: объект без currency проходит через учебный mapper, preview-order возвращает blocked, а предыдущий mapper в той же fixture даёт allowed. В нём нет реального трафика, логов, пользователей, внешних сервисов или боевого развёртывания. Сценарий нужен для одного вопроса: как записать цепочку так, чтобы временное действие не выдали за доказанную причину.

Учебная граница: один вход, один ожидаемый контракт

У каждого разбора должна быть граница. Здесь это не «оформление заказа вообще», а переход от mapper к pricing-adapter. Вход содержит сумму, но не содержит обязательное поле валюты. Результат blocked — первый наблюдаемый эффект. Он не говорит, что зависимость неисправна и не утверждает, что mapper уже виноват. Второе наблюдение говорит только то, что после преобразования currency отсутствует. Два наблюдения вместе дают право сформулировать H1, но не закрывают расследование сами по себе.

Это сужение может показаться слишком простым. Оно полезно именно потому, что не позволяет заполнить пробелы словами «иногда падает» или «сервис не отвечает». Чем шире симптом, тем больше случайных исправлений. Чем конкретнее граница, тем проще придумать проверку. В реальном проекте это может быть endpoint, очередь, миграция или сохранение файла. Но одна статья должна держать один контракт. Иначе временная шкала превращается в список разнородных событий, из которого нельзя сделать следующий безопасный шаг.

Учебный случай: от видимого эффекта к ограниченному действию
МоментЗафиксированный фактДопустимый выводЧто пока неизвестно
E1preview-order вернул blocked в fixtureесть симптом на границе путикакой компонент изменил вход
E2после mapper отсутствует currencyобязательное поле не дошло до contract boundaryгде поле впервые пропало
H1новый mapper мог не переносить currencyможно проверить конкретную ветку mapperчто это единственная причина
A1учебная ветка использует прежний mapperесть ограниченный обратимый обходчто причина устранена
V1fixture возвращает allowed и сохраняет полеA1 прошёл заданную проверкучто будущий mapper не повторит ошибку

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

Сначала запускаем маленькую модель, а не сочиняем журнал

Fixture должна быть читаемой без доступа к внутренним системам. В нашем случае она собирает обычный объект, получает blocked, затем проверяет цепочку событий. Код не подделывает timestamps из настоящего журнала и не повторяет сетевое поведение. Он показывает только значение, достаточное для E1 и E2. Это честная граница: можно проверить форму причинного рассуждения, но нельзя заявить, что изучены все внешние условия.

// Синтетический сценарий: данные существуют только в памяти Node.
const mappedInput = { amount: 100 };
const result = mappedInput.currency
  ? { state: 'allowed' }
  : { state: 'blocked', reason: 'currency-missing' };

// Это наблюдение E1/E2, не причина и не описание внешнего сервиса.
console.log(result);

После запуска такого кода результатом является не приговор компоненту, а два наблюдения. Первое — blocked. Второе — отсутствие currency. H1 появляется отдельной строкой, потому что она связывает их с mapper. Если завтра fixture изменится и поле окажется потеряно раньше, H1 удалят или заменят. E1 и E2 останутся полезными. Это простой тест качества разбора: можно ли выбросить гипотезу, не переписывая заодно фактическую часть истории?

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

Временное действие должно оставлять след

A1 в учебной линии выбирает предыдущий mapper. Это можно назвать обходом, но не исправлением причины. След остаётся в D1: trigger — H1, action — оставить известный вариант, expected check — V1. Такая запись помогает через день увидеть, что именно было принято и зачем. Если V1 оказался бы отрицательным, возвращаться к старому mapper было бы бессмысленно, и разбор пошёл бы к другой гипотезе. Без expected check команда рискует принять совпадение за результат действия.

Этот пример также показывает, почему не стоит одновременно менять retry, timeout и преобразование. В синтетическом случае retry мог бы снова послать объект без currency; timeout лишь сделал бы ожидание длиннее. Оба хода изменяют поведение, но не проверяют H1. Они могли бы быть оправданы при другой наблюдаемой причине, однако тогда должны появиться новые строки в шкале и отдельные проверки. Разбор не запрещает инструменты. Он требует, чтобы инструмент соответствовал записанной границе.

const decision = {
  id: 'D1',
  trigger: 'H1',
  action: 'оставить старый mapper в учебной ветке',
  expectedCheck: 'V1',
};

// Запрещённый вывод: «mapper виноват».
// Проверяемый вывод: H1 основана на E2 и ожидает V1.

Профилактика начинается с нового проверяемого контракта

После V1 легко написать «добавим тест» и закрыть вопрос. Вместо этого в fixture есть P1: contract fixture отклоняет mapper без currency. У P1 есть точка запуска — runIncidentFixture() — и status planned. Второй пункт P2 фиксирует обязательное поле в месте, где mapper передаёт данные в pricing-adapter, и предлагает ревью на маленьком входном примере. Это две разные профилактики: автоматический барьер и явность контракта.

Planned здесь не формальность. В нём скрыт полезный вопрос: что изменится до следующей попытки? Пока тест не добавлен и не прошёл, разбор не может обещать, что поле не исчезнет снова. Пока контракт не стал видимым, ревьюер может не заметить регрессию. Разделение временного обхода и профилактики возвращает работе реальный масштаб: A1 уменьшил влияние учебного случая сейчас, P1/P2 должны уменьшить вероятность повторения после отдельной реализации и проверки.

const fixture = runIncidentFixture();
if (!Object.values(fixture.assertions).every(Boolean)) {
  throw new Error('controlled incident contract failed');
}

console.log(fixture.timeline.map((event) => event.id));
// ['E1', 'E2', 'H1', 'A1', 'V1']

Запуск fixture проверяет семь простых утверждений: порядок timeline, связь H1 с E2, связь A1 с H1, связь V1 с A1, expected check у D1, конкретность P1/P2 и отсутствие имён людей. Это не интеграционный тест. Он не проверяет сеть, доступы, базу, browser, deployment или поведение внешнего адаптера. Его ценность в другом: будущая редактура текста или кода не сможет заменить проверяемую последовательность на убедительный рассказ без ссылок.

Как читать отрицательный результат без поиска виноватого

Допустим, V1 не прошёл. Это не повод написать, что предыдущий mapper тоже «плохой». Это новый факт: A1 не подтвердил ожидаемый эффект. Дальше надо расширить или изменить H1 и снять новое наблюдение. Возможно, currency отсутствует ещё во входном объекте. Возможно, blocked вызван другим полем. Важно, что запись сохраняет путь: неудачный обход не прячется, а показывает, какое предположение не выдержало проверки.

Такой язык убирает обвинение не ради вежливости. Он сохраняет технические данные для следующей попытки. Вместо «кто-то сломал mapper» остаётся вопрос «какое преобразование нарушает обязательность currency и где должна сработать проверка». Вместо «мы всё исправили» остаётся «A1 подтвердился для fixture; P1/P2 пока planned». Эти формулировки короче, точнее и полезнее для автора следующего изменения.

Маршрут разбора синтетического случая

  1. Назвать одну границу и один нежелательный эффект. В упражнении это preview-order и состояние blocked.
  2. Собрать наблюдения до действия: результат вызова и значение обязательного поля после преобразования.
  3. Сформулировать H1 как проверяемую связь с конкретным observation, а не как окончательный диагноз.
  4. Выбрать обратимое действие, которое относится к H1. Записать его отдельно от причины и указать expected check.
  5. Запустить контролируемую fixture и прочитать V1 только в пределах её входа. Не расширять вывод на неизвестные пути.
  6. Создать P1/P2 с ясным изменением и будущей проверкой; оставить их planned до реальной реализации.
  7. При следующем изменении прогнать fixture снова и дополнить временную шкалу новым observation, если контракт нарушен иначе.

Что эта заметка не утверждает

Учебный случай не оценивает нагрузку, доступность, время восстановления или поведение реальной зависимости. В нём нет заявлений о production-случае, реальном трафике, людях, логах или развертывании. Не было запущено внешнее API, browser, очередь, база, proxy или monitoring. Поэтому его нельзя использовать как отчёт о существующей системе. Его можно использовать как маленький шаблон мысли, прежде чем идти собирать факты в конкретной среде с её правами, версией и рисками.

Материалы Google и NIST ниже не дают одну универсальную форму отчёта. Они подтверждают более общий принцип: зафиксировать состояние, анализировать evidence, ограничивать реакцию и оставлять follow-up. Для автора конца 2020 года достаточно научиться переносить этот принцип в локальный код и проверяемую fixture. Следующая взрослая ступень появится только после накопления настоящих измерений и командной практики, а не после того, как в текст добавят модные термины.

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

  • Google SRE Book: Managing Incidents — официальная глава 2017 года о фиксации живого состояния инцидента, разделении работ и восстановлении; это источник принципов, а не описание данного учебного случая
  • Google SRE Book: Postmortem Culture — официальное описание postmortem как записи влияния, действий, причин и follow-up; особенно полезно различение системных причин и персонального обвинения
  • NIST SP 800-61 Rev. 2: Computer Security Incident Handling Guide — первичный нормативный источник для security-инцидентов; здесь используется только его общий цикл подготовки, обнаружения, анализа, сдерживания и извлечения уроков
  • Google SRE Book: Effective Troubleshooting — официальный материал о проверяемых гипотезах, наблюдениях и ценности отрицательного результата при поиске причины